mirror of
https://github.com/therootcompany/telebit.git
synced 2025-05-12 09:26:34 +00:00
vendor deps
This commit is contained in:
parent
51e6e84303
commit
9f0eab6eca
402
vendor/github.com/caddyserver/certmagic/acmeclient.go
generated
vendored
402
vendor/github.com/caddyserver/certmagic/acmeclient.go
generated
vendored
@ -1,402 +0,0 @@
|
||||
// Copyright 2015 Matthew Holt
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package certmagic
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"log"
|
||||
weakrand "math/rand"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/go-acme/lego/v3/acme"
|
||||
"github.com/go-acme/lego/v3/certificate"
|
||||
"github.com/go-acme/lego/v3/challenge"
|
||||
"github.com/go-acme/lego/v3/lego"
|
||||
"github.com/go-acme/lego/v3/registration"
|
||||
)
|
||||
|
||||
func init() {
|
||||
weakrand.Seed(time.Now().UnixNano())
|
||||
}
|
||||
|
||||
// acmeClient is a wrapper over lego's acme.Client with
|
||||
// some custom state attached. It is used to obtain,
|
||||
// renew, and revoke certificates with ACME. Use
|
||||
// ACMEManager.newACMEClient() or
|
||||
// ACMEManager.newACMEClientWithRetry() to get a valid
|
||||
// one for real use.
|
||||
type acmeClient struct {
|
||||
caURL string
|
||||
mgr *ACMEManager
|
||||
acmeClient *lego.Client
|
||||
challenges []challenge.Type
|
||||
}
|
||||
|
||||
// newACMEClientWithRetry is the same as newACMEClient, but with
|
||||
// automatic retry capabilities. Sometimes network connections or
|
||||
// HTTP requests fail intermittently, even when requesting the
|
||||
// directory endpoint for example, so we can avoid that by just
|
||||
// retrying once. Failures here are rare and sporadic, usually,
|
||||
// so a simple retry is an easy fix.
|
||||
func (am *ACMEManager) newACMEClientWithRetry(useTestCA bool) (*acmeClient, error) {
|
||||
var client *acmeClient
|
||||
var err error
|
||||
const maxTries = 2
|
||||
for i := 0; i < maxTries; i++ {
|
||||
if i > 0 {
|
||||
time.Sleep(2 * time.Second)
|
||||
}
|
||||
client, err = am.newACMEClient(useTestCA, false) // TODO: move logic that requires interactivity to way before this part of the process...
|
||||
if err == nil {
|
||||
break
|
||||
}
|
||||
if acmeErr, ok := err.(acme.ProblemDetails); ok {
|
||||
if acmeErr.HTTPStatus == http.StatusTooManyRequests {
|
||||
return nil, fmt.Errorf("too many requests making new ACME client: %+v - aborting", acmeErr)
|
||||
}
|
||||
}
|
||||
log.Printf("[ERROR] Making new ACME client: %v (attempt %d/%d)", err, i+1, maxTries)
|
||||
}
|
||||
return client, err
|
||||
}
|
||||
|
||||
// newACMEClient creates the underlying ACME library client type.
|
||||
// If useTestCA is true, am.TestCA will be used if it is set;
|
||||
// otherwise, the primary CA will still be used.
|
||||
func (am *ACMEManager) newACMEClient(useTestCA, interactive bool) (*acmeClient, error) {
|
||||
acmeClientsMu.Lock()
|
||||
defer acmeClientsMu.Unlock()
|
||||
|
||||
// ensure defaults are filled in
|
||||
certObtainTimeout := am.CertObtainTimeout
|
||||
if certObtainTimeout == 0 {
|
||||
certObtainTimeout = DefaultACME.CertObtainTimeout
|
||||
}
|
||||
var caURL string
|
||||
if useTestCA {
|
||||
caURL = am.TestCA
|
||||
// only use the default test CA if the CA is also
|
||||
// the default CA; no point in testing against
|
||||
// Let's Encrypt's staging server if we are not
|
||||
// using their production server too
|
||||
if caURL == "" && am.CA == DefaultACME.CA {
|
||||
caURL = DefaultACME.TestCA
|
||||
}
|
||||
}
|
||||
if caURL == "" {
|
||||
caURL = am.CA
|
||||
}
|
||||
if caURL == "" {
|
||||
caURL = DefaultACME.CA
|
||||
}
|
||||
|
||||
// ensure endpoint is secure (assume HTTPS if scheme is missing)
|
||||
if !strings.Contains(caURL, "://") {
|
||||
caURL = "https://" + caURL
|
||||
}
|
||||
u, err := url.Parse(caURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if u.Scheme != "https" && !isLoopback(u.Host) && !isInternal(u.Host) {
|
||||
return nil, fmt.Errorf("%s: insecure CA URL (HTTPS required)", caURL)
|
||||
}
|
||||
|
||||
// look up or create the user account
|
||||
leUser, err := am.getUser(caURL, am.Email)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// if a lego client with this configuration already exists, reuse it
|
||||
clientKey := caURL + leUser.Email
|
||||
client, ok := acmeClients[clientKey]
|
||||
if !ok {
|
||||
// the client facilitates our communication with the CA server
|
||||
legoCfg := lego.NewConfig(leUser)
|
||||
legoCfg.CADirURL = caURL
|
||||
legoCfg.UserAgent = buildUAString()
|
||||
legoCfg.HTTPClient.Timeout = HTTPTimeout
|
||||
legoCfg.Certificate = lego.CertificateConfig{
|
||||
Timeout: am.CertObtainTimeout,
|
||||
}
|
||||
if am.TrustedRoots != nil {
|
||||
if ht, ok := legoCfg.HTTPClient.Transport.(*http.Transport); ok {
|
||||
if ht.TLSClientConfig == nil {
|
||||
ht.TLSClientConfig = new(tls.Config)
|
||||
ht.ForceAttemptHTTP2 = true
|
||||
}
|
||||
ht.TLSClientConfig.RootCAs = am.TrustedRoots
|
||||
}
|
||||
}
|
||||
client, err = lego.NewClient(legoCfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
acmeClients[clientKey] = client
|
||||
}
|
||||
|
||||
// if not registered, the user must register an account
|
||||
// with the CA and agree to terms
|
||||
if leUser.Registration == nil {
|
||||
if interactive { // can't prompt a user who isn't there
|
||||
termsURL := client.GetToSURL()
|
||||
if !am.Agreed && termsURL != "" {
|
||||
am.Agreed = am.askUserAgreement(client.GetToSURL())
|
||||
}
|
||||
if !am.Agreed && termsURL != "" {
|
||||
return nil, fmt.Errorf("user must agree to CA terms")
|
||||
}
|
||||
}
|
||||
|
||||
var reg *registration.Resource
|
||||
if am.ExternalAccount != nil {
|
||||
reg, err = client.Registration.RegisterWithExternalAccountBinding(registration.RegisterEABOptions{
|
||||
TermsOfServiceAgreed: am.Agreed,
|
||||
Kid: am.ExternalAccount.KeyID,
|
||||
HmacEncoded: base64.StdEncoding.EncodeToString(am.ExternalAccount.HMAC),
|
||||
})
|
||||
} else {
|
||||
reg, err = client.Registration.Register(registration.RegisterOptions{
|
||||
TermsOfServiceAgreed: am.Agreed,
|
||||
})
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
leUser.Registration = reg
|
||||
|
||||
// persist the user to storage
|
||||
err = am.saveUser(caURL, leUser)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not save user: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
c := &acmeClient{
|
||||
caURL: caURL,
|
||||
mgr: am,
|
||||
acmeClient: client,
|
||||
}
|
||||
|
||||
return c, nil
|
||||
}
|
||||
|
||||
// initialChallenges returns the initial set of challenges
|
||||
// to try using c.config as a basis.
|
||||
func (c *acmeClient) initialChallenges() []challenge.Type {
|
||||
// if configured, use DNS challenge exclusively
|
||||
if c.mgr.DNSProvider != nil {
|
||||
return []challenge.Type{challenge.DNS01}
|
||||
}
|
||||
|
||||
// otherwise, use HTTP and TLS-ALPN challenges if enabled
|
||||
var chal []challenge.Type
|
||||
if !c.mgr.DisableHTTPChallenge {
|
||||
chal = append(chal, challenge.HTTP01)
|
||||
}
|
||||
if !c.mgr.DisableTLSALPNChallenge {
|
||||
chal = append(chal, challenge.TLSALPN01)
|
||||
}
|
||||
return chal
|
||||
}
|
||||
|
||||
// nextChallenge chooses a challenge randomly from the given list of
|
||||
// available challenges and configures c.acmeClient to use that challenge
|
||||
// according to c.config. It pops the chosen challenge from the list and
|
||||
// returns that challenge along with the new list without that challenge.
|
||||
// If len(available) == 0, this is a no-op.
|
||||
//
|
||||
// Don't even get me started on how dumb it is we need to do this here
|
||||
// instead of the upstream lego library doing it for us. Lego used to
|
||||
// randomize the challenge order, thus allowing another one to be used
|
||||
// if the first one failed. https://github.com/go-acme/lego/issues/842
|
||||
// (It also has an awkward API for adjusting the available challenges.)
|
||||
// At time of writing, lego doesn't try anything other than the TLS-ALPN
|
||||
// challenge, even if the HTTP challenge is also enabled. So we take
|
||||
// matters into our own hands and enable only one challenge at a time
|
||||
// in the underlying client, randomly selected by us.
|
||||
func (c *acmeClient) nextChallenge(available []challenge.Type) (challenge.Type, []challenge.Type) {
|
||||
if len(available) == 0 {
|
||||
return "", available
|
||||
}
|
||||
|
||||
// make sure we choose a challenge randomly, which lego used to do but
|
||||
// the critical feature was surreptitiously removed in ~2018 in a commit
|
||||
// too large to review, oh well - choose one, then remove it from the
|
||||
// list of available challenges so it doesn't get retried
|
||||
randIdx := weakrand.Intn(len(available))
|
||||
randomChallenge := available[randIdx]
|
||||
available = append(available[:randIdx], available[randIdx+1:]...)
|
||||
|
||||
// clean the slate, since we reuse clients
|
||||
c.acmeClient.Challenge.Remove(challenge.HTTP01)
|
||||
c.acmeClient.Challenge.Remove(challenge.TLSALPN01)
|
||||
c.acmeClient.Challenge.Remove(challenge.DNS01)
|
||||
|
||||
switch randomChallenge {
|
||||
case challenge.HTTP01:
|
||||
useHTTPPort := HTTPChallengePort
|
||||
if HTTPPort > 0 && HTTPPort != HTTPChallengePort {
|
||||
useHTTPPort = HTTPPort
|
||||
}
|
||||
if c.mgr.AltHTTPPort > 0 {
|
||||
useHTTPPort = c.mgr.AltHTTPPort
|
||||
}
|
||||
|
||||
c.acmeClient.Challenge.SetHTTP01Provider(distributedSolver{
|
||||
acmeManager: c.mgr,
|
||||
providerServer: &httpSolver{
|
||||
acmeManager: c.mgr,
|
||||
address: net.JoinHostPort(c.mgr.ListenHost, strconv.Itoa(useHTTPPort)),
|
||||
},
|
||||
caURL: c.caURL,
|
||||
})
|
||||
|
||||
case challenge.TLSALPN01:
|
||||
useTLSALPNPort := TLSALPNChallengePort
|
||||
if HTTPSPort > 0 && HTTPSPort != TLSALPNChallengePort {
|
||||
useTLSALPNPort = HTTPSPort
|
||||
}
|
||||
if c.mgr.AltTLSALPNPort > 0 {
|
||||
useTLSALPNPort = c.mgr.AltTLSALPNPort
|
||||
}
|
||||
|
||||
c.acmeClient.Challenge.SetTLSALPN01Provider(distributedSolver{
|
||||
acmeManager: c.mgr,
|
||||
providerServer: &tlsALPNSolver{
|
||||
config: c.mgr.config,
|
||||
address: net.JoinHostPort(c.mgr.ListenHost, strconv.Itoa(useTLSALPNPort)),
|
||||
},
|
||||
caURL: c.caURL,
|
||||
})
|
||||
|
||||
case challenge.DNS01:
|
||||
if c.mgr.DNSChallengeOption != nil {
|
||||
c.acmeClient.Challenge.SetDNS01Provider(c.mgr.DNSProvider, c.mgr.DNSChallengeOption)
|
||||
} else {
|
||||
c.acmeClient.Challenge.SetDNS01Provider(c.mgr.DNSProvider)
|
||||
}
|
||||
}
|
||||
|
||||
return randomChallenge, available
|
||||
}
|
||||
|
||||
func (c *acmeClient) throttle(ctx context.Context, names []string) error {
|
||||
// throttling is scoped to CA + account email
|
||||
rateLimiterKey := c.caURL + "," + c.mgr.Email
|
||||
rateLimitersMu.Lock()
|
||||
rl, ok := rateLimiters[rateLimiterKey]
|
||||
if !ok {
|
||||
rl = NewRateLimiter(RateLimitEvents, RateLimitEventsWindow)
|
||||
rateLimiters[rateLimiterKey] = rl
|
||||
// TODO: stop rate limiter when it is garbage-collected...
|
||||
}
|
||||
rateLimitersMu.Unlock()
|
||||
log.Printf("[INFO]%v Waiting on rate limiter...", names)
|
||||
err := rl.Wait(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
log.Printf("[INFO]%v Done waiting", names)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *acmeClient) usingTestCA() bool {
|
||||
return c.mgr.TestCA != "" && c.caURL == c.mgr.TestCA
|
||||
}
|
||||
|
||||
func (c *acmeClient) revoke(_ context.Context, certRes certificate.Resource) error {
|
||||
return c.acmeClient.Certificate.Revoke(certRes.Certificate)
|
||||
}
|
||||
|
||||
func buildUAString() string {
|
||||
ua := "CertMagic"
|
||||
if UserAgent != "" {
|
||||
ua += " " + UserAgent
|
||||
}
|
||||
return ua
|
||||
}
|
||||
|
||||
// These internal rate limits are designed to prevent accidentally
|
||||
// firehosing a CA's ACME endpoints. They are not intended to
|
||||
// replace or replicate the CA's actual rate limits.
|
||||
//
|
||||
// Let's Encrypt's rate limits can be found here:
|
||||
// https://letsencrypt.org/docs/rate-limits/
|
||||
//
|
||||
// Currently (as of December 2019), Let's Encrypt's most relevant
|
||||
// rate limit for large deployments is 300 new orders per account
|
||||
// per 3 hours (on average, or best case, that's about 1 every 36
|
||||
// seconds, or 2 every 72 seconds, etc.); but it's not reasonable
|
||||
// to try to assume that our internal state is the same as the CA's
|
||||
// (due to process restarts, config changes, failed validations,
|
||||
// etc.) and ultimately, only the CA's actual rate limiter is the
|
||||
// authority. Thus, our own rate limiters do not attempt to enforce
|
||||
// external rate limits. Doing so causes problems when the domains
|
||||
// are not in our control (i.e. serving customer sites) and/or lots
|
||||
// of domains fail validation: they clog our internal rate limiter
|
||||
// and nearly starve out (or at least slow down) the other domains
|
||||
// that need certificates. Failed transactions are already retried
|
||||
// with exponential backoff, so adding in rate limiting can slow
|
||||
// things down even more.
|
||||
//
|
||||
// Instead, the point of our internal rate limiter is to avoid
|
||||
// hammering the CA's endpoint when there are thousands or even
|
||||
// millions of certificates under management. Our goal is to
|
||||
// allow small bursts in a relatively short timeframe so as to
|
||||
// not block any one domain for too long, without unleashing
|
||||
// thousands of requests to the CA at once.
|
||||
var (
|
||||
rateLimiters = make(map[string]*RingBufferRateLimiter)
|
||||
rateLimitersMu sync.RWMutex
|
||||
|
||||
// RateLimitEvents is how many new events can be allowed
|
||||
// in RateLimitEventsWindow.
|
||||
RateLimitEvents = 10
|
||||
|
||||
// RateLimitEventsWindow is the size of the sliding
|
||||
// window that throttles events.
|
||||
RateLimitEventsWindow = 1 * time.Minute
|
||||
)
|
||||
|
||||
// Some default values passed down to the underlying lego client.
|
||||
var (
|
||||
UserAgent string
|
||||
HTTPTimeout = 30 * time.Second
|
||||
)
|
||||
|
||||
// We keep a global cache of ACME clients so that they
|
||||
// can be reused. Since the number of CAs, accounts,
|
||||
// and key types should be fairly limited under best
|
||||
// practices, this map will hardly ever have more than
|
||||
// a few entries at the most. The associated lock
|
||||
// protects access to the map but also ensures that only
|
||||
// one ACME client is created at a time.
|
||||
// TODO: consider using storage for a distributed lock
|
||||
// TODO: consider evicting clients after some time
|
||||
var (
|
||||
acmeClients = make(map[string]*lego.Client)
|
||||
acmeClientsMu sync.Mutex
|
||||
)
|
85
vendor/github.com/caddyserver/certmagic/azure-pipelines.yml
generated
vendored
85
vendor/github.com/caddyserver/certmagic/azure-pipelines.yml
generated
vendored
@ -1,85 +0,0 @@
|
||||
trigger:
|
||||
- master
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
linux:
|
||||
imageName: ubuntu-16.04
|
||||
gorootDir: /usr/local
|
||||
mac:
|
||||
imageName: macos-10.13
|
||||
gorootDir: /usr/local
|
||||
windows:
|
||||
imageName: windows-2019
|
||||
gorootDir: C:\
|
||||
|
||||
pool:
|
||||
vmImage: $(imageName)
|
||||
|
||||
variables:
|
||||
GOROOT: $(gorootDir)/go
|
||||
GOPATH: $(system.defaultWorkingDirectory)/gopath
|
||||
GOBIN: $(GOPATH)/bin
|
||||
modulePath: '$(GOPATH)/src/github.com/$(build.repository.name)'
|
||||
# TODO: modules should be the default in Go 1.13, so this won't be needed
|
||||
#GO111MODULE: on
|
||||
|
||||
steps:
|
||||
- bash: |
|
||||
latestGo=$(curl "https://golang.org/VERSION?m=text")
|
||||
echo "##vso[task.setvariable variable=LATEST_GO]$latestGo"
|
||||
echo "Latest Go version: $latestGo"
|
||||
displayName: "Get latest Go version"
|
||||
|
||||
- bash: |
|
||||
sudo rm -f $(which go)
|
||||
echo '##vso[task.prependpath]$(GOBIN)'
|
||||
echo '##vso[task.prependpath]$(GOROOT)/bin'
|
||||
mkdir -p '$(modulePath)'
|
||||
shopt -s extglob
|
||||
shopt -s dotglob
|
||||
mv !(gopath) '$(modulePath)'
|
||||
displayName: Remove old Go, set GOBIN/GOROOT, and move project into GOPATH
|
||||
|
||||
# Install Go (this varies by platform)
|
||||
|
||||
- bash: |
|
||||
wget "https://dl.google.com/go/$(LATEST_GO).linux-amd64.tar.gz"
|
||||
sudo tar -C $(gorootDir) -xzf "$(LATEST_GO).linux-amd64.tar.gz"
|
||||
condition: eq( variables['Agent.OS'], 'Linux' )
|
||||
displayName: Install Go on Linux
|
||||
|
||||
- bash: |
|
||||
wget "https://dl.google.com/go/$(LATEST_GO).darwin-amd64.tar.gz"
|
||||
sudo tar -C $(gorootDir) -xzf "$(LATEST_GO).darwin-amd64.tar.gz"
|
||||
condition: eq( variables['Agent.OS'], 'Darwin' )
|
||||
displayName: Install Go on macOS
|
||||
|
||||
- powershell: |
|
||||
Write-Host "Downloading Go... (please be patient, I am very slow)"
|
||||
(New-Object System.Net.WebClient).DownloadFile("https://dl.google.com/go/$(LATEST_GO).windows-amd64.zip", "$(LATEST_GO).windows-amd64.zip")
|
||||
Write-Host "Extracting Go... (I'm slow too)"
|
||||
Expand-Archive "$(LATEST_GO).windows-amd64.zip" -DestinationPath "$(gorootDir)"
|
||||
condition: eq( variables['Agent.OS'], 'Windows_NT' )
|
||||
displayName: Install Go on Windows
|
||||
|
||||
# TODO: When this issue is fixed, replace with installer script:
|
||||
# https://github.com/golangci/golangci-lint/issues/472
|
||||
- script: go get -v github.com/golangci/golangci-lint/cmd/golangci-lint
|
||||
displayName: Install golangci-lint
|
||||
|
||||
- bash: |
|
||||
printf "Using go at: $(which go)\n"
|
||||
printf "Go version: $(go version)\n"
|
||||
printf "\n\nGo environment:\n\n"
|
||||
go env
|
||||
printf "\n\nSystem environment:\n\n"
|
||||
env
|
||||
displayName: Print Go version and environment
|
||||
|
||||
- script: |
|
||||
go get -v -t -d ./...
|
||||
golangci-lint run -E gofmt -E goimports -E misspell
|
||||
go test -race ./...
|
||||
workingDirectory: '$(modulePath)'
|
||||
displayName: Run tests
|
9
vendor/github.com/caddyserver/certmagic/go.mod
generated
vendored
9
vendor/github.com/caddyserver/certmagic/go.mod
generated
vendored
@ -1,9 +0,0 @@
|
||||
module github.com/caddyserver/certmagic
|
||||
|
||||
go 1.13
|
||||
|
||||
require (
|
||||
github.com/go-acme/lego/v3 v3.4.0
|
||||
github.com/klauspost/cpuid v1.2.3
|
||||
golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073
|
||||
)
|
380
vendor/github.com/caddyserver/certmagic/go.sum
generated
vendored
380
vendor/github.com/caddyserver/certmagic/go.sum
generated
vendored
@ -1,380 +0,0 @@
|
||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
|
||||
cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
|
||||
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
|
||||
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
|
||||
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
|
||||
cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
|
||||
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
|
||||
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
|
||||
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
|
||||
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
|
||||
contrib.go.opencensus.io/exporter/ocagent v0.4.12/go.mod h1:450APlNTSR6FrvC3CTRqYosuDstRB9un7SOx2k/9ckA=
|
||||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
||||
github.com/Azure/azure-sdk-for-go v32.4.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
|
||||
github.com/Azure/go-autorest/autorest v0.1.0/go.mod h1:AKyIcETwSUFxIcs/Wnq/C+kwCtlEYGUVd7FPNb2slmg=
|
||||
github.com/Azure/go-autorest/autorest v0.5.0/go.mod h1:9HLKlQjVBH6U3oDfsXOeVc56THsLPw1L03yban4xThw=
|
||||
github.com/Azure/go-autorest/autorest/adal v0.1.0/go.mod h1:MeS4XhScH55IST095THyTxElntu7WqB7pNbZo8Q5G3E=
|
||||
github.com/Azure/go-autorest/autorest/adal v0.2.0/go.mod h1:MeS4XhScH55IST095THyTxElntu7WqB7pNbZo8Q5G3E=
|
||||
github.com/Azure/go-autorest/autorest/azure/auth v0.1.0/go.mod h1:Gf7/i2FUpyb/sGBLIFxTBzrNzBo7aPXXE3ZVeDRwdpM=
|
||||
github.com/Azure/go-autorest/autorest/azure/cli v0.1.0/go.mod h1:Dk8CUAt/b/PzkfeRsWzVG9Yj3ps8mS8ECztu43rdU8U=
|
||||
github.com/Azure/go-autorest/autorest/date v0.1.0/go.mod h1:plvfp3oPSKwf2DNjlBjWF/7vwR+cUD/ELuzDCXwHUVA=
|
||||
github.com/Azure/go-autorest/autorest/mocks v0.1.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0=
|
||||
github.com/Azure/go-autorest/autorest/to v0.2.0/go.mod h1:GunWKJp1AEqgMaGLV+iocmRAJWqST1wQYhyyjXJ3SJc=
|
||||
github.com/Azure/go-autorest/autorest/validation v0.1.0/go.mod h1:Ha3z/SqBeaalWQvokg3NZAlQTalVMtOIAs1aGK7G6u8=
|
||||
github.com/Azure/go-autorest/logger v0.1.0/go.mod h1:oExouG+K6PryycPJfVSxi/koC6LSNgds39diKLz7Vrc=
|
||||
github.com/Azure/go-autorest/tracing v0.1.0/go.mod h1:ROEEAFwXycQw7Sn3DXNtEedEvdeRAgDr0izn4z5Ij88=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||
github.com/OpenDNS/vegadns2client v0.0.0-20180418235048-a3fa4a771d87/go.mod h1:iGLljf5n9GjT6kc0HBvyI1nOKnGQbNB66VzSNbK5iks=
|
||||
github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo=
|
||||
github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=
|
||||
github.com/akamai/AkamaiOPEN-edgegrid-golang v0.9.0/go.mod h1:zpDJeKyp9ScW4NNrbdr+Eyxvry3ilGPewKoXw3XGN1k=
|
||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||
github.com/aliyun/alibaba-cloud-sdk-go v0.0.0-20190808125512-07798873deee/go.mod h1:myCDvQSzCW+wB1WAlocEru4wMGJxy+vlxHdhegi1CDQ=
|
||||
github.com/aliyun/aliyun-oss-go-sdk v0.0.0-20190307165228-86c17b95fcd5/go.mod h1:T/Aws4fEfogEE9v+HPhhw+CntffsBHJ8nXQCwKr0/g8=
|
||||
github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
|
||||
github.com/aws/aws-sdk-go v1.23.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
|
||||
github.com/baiyubin/aliyun-sts-go-sdk v0.0.0-20180326062324-cfa1a18b161f/go.mod h1:AuiFmCCPBSrqvVMvuqFuk0qogytodnVFVSN5CeJB8Gc=
|
||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
|
||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||
github.com/cenkalti/backoff/v4 v4.0.0 h1:6VeaLF9aI+MAUQ95106HwWzYZgJJpZ4stumjj6RFYAU=
|
||||
github.com/cenkalti/backoff/v4 v4.0.0/go.mod h1:eEew/i+1Q6OrCDZh3WiXYv3+nJwBASZ8Bog/87DQnVg=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/cloudflare/cloudflare-go v0.10.2/go.mod h1:qhVI5MKwBGhdNU89ZRz2plgYutcJ5PCekLxXn56w6SY=
|
||||
github.com/cpu/goacmedns v0.0.1/go.mod h1:sesf/pNnCYwUevQEQfEwY0Y3DydlQWSGZbaMElOWxok=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
|
||||
github.com/dimchansky/utfbom v1.1.0/go.mod h1:rO41eb7gLfo8SF1jd9F8HplJm1Fewwi4mQvIirEdv+8=
|
||||
github.com/dnaeon/go-vcr v0.0.0-20180814043457-aafff18a5cc2/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E=
|
||||
github.com/dnsimple/dnsimple-go v0.30.0/go.mod h1:O5TJ0/U6r7AfT8niYNlmohpLbCSG+c71tQlGr9SeGrg=
|
||||
github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs=
|
||||
github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU=
|
||||
github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
|
||||
github.com/exoscale/egoscale v0.18.1/go.mod h1:Z7OOdzzTOz1Q1PjQXumlz9Wn/CddH0zSYdCF3rnBKXE=
|
||||
github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/go-acme/lego/v3 v3.4.0 h1:deB9NkelA+TfjGHVw8J7iKl/rMtffcGMWSMmptvMv0A=
|
||||
github.com/go-acme/lego/v3 v3.4.0/go.mod h1:xYbLDuxq3Hy4bMUT1t9JIuz6GWIWb3m5X+TeTHYaT7M=
|
||||
github.com/go-cmd/cmd v1.0.5/go.mod h1:y8q8qlK5wQibcw63djSl/ntiHUHXHGdCkPk0j4QeW4s=
|
||||
github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
github.com/go-ini/ini v1.44.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=
|
||||
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
|
||||
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||
github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
|
||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
github.com/goji/httpauth v0.0.0-20160601135302-2da839ab0f4d/go.mod h1:nnjvkQ9ptGaCkuDUx6wNykzzlUixGxvkme+H/lnzb+A=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
||||
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
||||
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
||||
github.com/gophercloud/gophercloud v0.3.0/go.mod h1:vxM41WHh5uqHVBMZHzuwNOHh8XEoIEcSTewFxm1c5g8=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
|
||||
github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
|
||||
github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.8.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
|
||||
github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542/go.mod h1:Ow0tF8D4Kplbc8s8sSb3V2oUCygFHVp8gC3Dn6U4MNI=
|
||||
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hashicorp/golang-lru v0.5.3/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
github.com/iij/doapi v0.0.0-20190504054126-0bbf12d6d7df/go.mod h1:QMZY7/J/KSQEhKWFeDesPjMj+wCHReeknARU3wqlyN4=
|
||||
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
|
||||
github.com/json-iterator/go v1.1.5/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||
github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
||||
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
||||
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/klauspost/cpuid v1.2.3 h1:CCtW0xUnWGVINKvE/WWOYKdsPV6mawAtvQuSl8guwQs=
|
||||
github.com/klauspost/cpuid v1.2.3/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
|
||||
github.com/kolo/xmlrpc v0.0.0-20190717152603-07c4ee3fd181/go.mod h1:o03bZfuBwAXHetKXuInt4S7omeXUu62/A845kiycsSQ=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
||||
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/labbsr0x/bindman-dns-webhook v1.0.2/go.mod h1:p6b+VCXIR8NYKpDr8/dg1HKfQoRHCdcsROXKvmoehKA=
|
||||
github.com/labbsr0x/goh v1.0.1/go.mod h1:8K2UhVoaWXcCU7Lxoa2omWnC8gyW8px7/lmO61c027w=
|
||||
github.com/linode/linodego v0.10.0/go.mod h1:cziNP7pbvE3mXIPneHj0oRY8L1WtGEIKlZ8LANE4eXA=
|
||||
github.com/liquidweb/liquidweb-go v1.6.0/go.mod h1:UDcVnAMDkZxpw4Y7NOHkqoeiGacVLEIG/i5J9cyixzQ=
|
||||
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
||||
github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
|
||||
github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
|
||||
github.com/mattn/go-tty v0.0.0-20180219170247-931426f7535a/go.mod h1:XPvLUNfbS4fJH25nqRHfWLMa1ONC8Amw+mIA639KxkE=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||
github.com/miekg/dns v1.1.15 h1:CSSIDtllwGLMoA6zjdKnaE6Tx6eVUxQ29LUgGetiDCI=
|
||||
github.com/miekg/dns v1.1.15/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
|
||||
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||
github.com/mitchellh/go-vnc v0.0.0-20150629162542-723ed9867aed/go.mod h1:3rdaFaCv4AyBgu5ALFM0+tSuHrBh6v692nyQe3ikrq0=
|
||||
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||
github.com/namedotcom/go v0.0.0-20180403034216-08470befbe04/go.mod h1:5sN+Lt1CaY4wsPvgQH/jsuJi4XO2ssZbdsIizr4CVC8=
|
||||
github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32/go.mod h1:9wM+0iRr9ahx58uYLpLIr5fm8diHn0JbqRycJi6w0Ms=
|
||||
github.com/nrdcg/auroradns v1.0.0/go.mod h1:6JPXKzIRzZzMqtTDgueIhTi6rFf1QvYE/HzqidhOhjw=
|
||||
github.com/nrdcg/dnspod-go v0.4.0/go.mod h1:vZSoFSFeQVm2gWLMkyX61LZ8HI3BaqtHZWgPTGKr6KQ=
|
||||
github.com/nrdcg/goinwx v0.6.1/go.mod h1:XPiut7enlbEdntAqalBIqcYcTEVhpv/dKWgDCX2SwKQ=
|
||||
github.com/nrdcg/namesilo v0.2.1/go.mod h1:lwMvfQTyYq+BbjJd30ylEG4GPSS6PII0Tia4rRpRiyw=
|
||||
github.com/olekukonko/tablewriter v0.0.1/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo=
|
||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
||||
github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw=
|
||||
github.com/oracle/oci-go-sdk v7.0.0+incompatible/go.mod h1:VQb79nF8Z2cwLkLS35ukwStZIg5F66tcBccjip/j888=
|
||||
github.com/ovh/go-ovh v0.0.0-20181109152953-ba5adb4cf014/go.mod h1:joRatxRJaZBsY3JAOEMcoOp05CnZzsx4scTxi95DHyQ=
|
||||
github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
|
||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
||||
github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs=
|
||||
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
|
||||
github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQP1xR9D75/vuwEF3g=
|
||||
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||
github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
||||
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
||||
github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc=
|
||||
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||
github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
|
||||
github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ=
|
||||
github.com/rainycape/memcache v0.0.0-20150622160815-1031fa0ce2f2/go.mod h1:7tZKcyumwBO6qip7RNQ5r77yrssm9bfCowcLEBcU5IA=
|
||||
github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
|
||||
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
|
||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/sacloud/libsacloud v1.26.1/go.mod h1:79ZwATmHLIFZIMd7sxA3LwzVy/B77uj3LDoToVTxDoQ=
|
||||
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||
github.com/skratchdot/open-golang v0.0.0-20160302144031-75fb7ed4208c/go.mod h1:sUM3LWHvSMaG192sy56D9F7CNvL7jUJVXoqM1QKLnog=
|
||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
||||
github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/timewasted/linode v0.0.0-20160829202747-37e84520dcf7/go.mod h1:imsgLplxEC/etjIhdr3dNzV3JeT27LbVu5pYWm0JCBY=
|
||||
github.com/transip/gotransip v0.0.0-20190812104329-6d8d9179b66f/go.mod h1:i0f4R4o2HM0m3DZYQWsj6/MEowD57VzoH0v3d7igeFY=
|
||||
github.com/uber-go/atomic v1.3.2/go.mod h1:/Ct5t2lcmbJ4OSe/waGBoaVvVqtO0bmtfVNex1PFV8g=
|
||||
github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
|
||||
github.com/vultr/govultr v0.1.4/go.mod h1:9H008Uxr/C4vFNGLqKx232C206GL0PBHzOP0809bGNA=
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
|
||||
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
|
||||
github.com/xeipuuv/gojsonschema v1.1.0/go.mod h1:5yf86TLmAcydyeJq5YvxkGPE2fm/u4myDekKRoLuqhs=
|
||||
go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
|
||||
go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
|
||||
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
||||
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
|
||||
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||
go.uber.org/ratelimit v0.0.0-20180316092928-c15da0234277/go.mod h1:2X8KaoNd1J0lZV+PxJk/5+DGbO/tpwLR1m++a7FnB/Y=
|
||||
golang.org/x/crypto v0.0.0-20180621125126-a49355c7e3f8/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190418165655-df01cb2cc480/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
|
||||
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550 h1:ObdrDkeb4kJdCP557AjRjq69pTHfNouLtWZG7j9rPN8=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073 h1:xMPOj6Pz6UipU1wXLkrtqpHbR0AVFnyPEQq/wRWz9lM=
|
||||
golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
|
||||
golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
|
||||
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
|
||||
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
|
||||
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
|
||||
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
|
||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
golang.org/x/net v0.0.0-20180611182652-db08ff08e862/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3 h1:ulvT7fqt0yHWzpJwI57MezWnYDVpCAYBVuYst/L+fAY=
|
||||
golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c h1:uOCk1iQW6Vc18bnC13MfzScl+wdKBmM9Y9kU7Z83/lw=
|
||||
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190930134127-c5a3c61f89f3 h1:6KET3Sqa7fkVfD63QnAM81ZeYg5n4HwApOJkufONnHA=
|
||||
golang.org/x/net v0.0.0-20190930134127-c5a3c61f89f3/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20191027093000-83d349e8ac1a h1:Yu34BogBivvmu7SAzHHaB9nZWH5D1C+z3F1jyIaYZSQ=
|
||||
golang.org/x/net v0.0.0-20191027093000-83d349e8ac1a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4 h1:YUO/7uOKsKeq9UokNS62b8FYywz3ker1l1vDZRCRefw=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e h1:vcxGaoTs7kV8m5Np9uUNQin4BrLOthgV7252N8V+FwY=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180622082034-63fc586f45fe/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190209173611-3b5209105503/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b h1:ag/x1USPSsqHud38I9BAC88qdNLDHHtQ4mlgQIZPPNA=
|
||||
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3 h1:4y9KwBHBgBNwDbtu44R5o1fdOCQUEXhbk/P4A9WmJq0=
|
||||
golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20190921001708-c4c64cad1fd0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk=
|
||||
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
|
||||
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
|
||||
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
||||
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
||||
google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||
google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
|
||||
google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.19.1/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
||||
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||
gopkg.in/h2non/gock.v1 v1.0.15/go.mod h1:sX4zAkdYX1TRGJ2JY156cFspQn4yRWn6p9EMdODlynE=
|
||||
gopkg.in/ini.v1 v1.42.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/ini.v1 v1.44.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/ns1/ns1-go.v2 v2.0.0-20190730140822-b51389932cbc/go.mod h1:VV+3haRsgDiVLxyifmMBrBIuCWFBPYKbRssXB9z67Hw=
|
||||
gopkg.in/resty.v1 v1.9.1/go.mod h1:vo52Hzryw9PnPHcJfPsBiFW62XhNx5OczbV9y+IMpgc=
|
||||
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
|
||||
gopkg.in/square/go-jose.v2 v2.3.1 h1:SK5KegNXmKmqE342YYN2qPHEnUYeoMiXXl1poUlI+o4=
|
||||
gopkg.in/square/go-jose.v2 v2.3.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
|
||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
||||
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
|
@ -5,7 +5,7 @@
|
||||
<p align="center">The same library used by the <a href="https://caddyserver.com">Caddy Web Server</a></p>
|
||||
<p align="center">
|
||||
<a href="https://pkg.go.dev/github.com/caddyserver/certmagic?tab=doc"><img src="https://img.shields.io/badge/godoc-reference-blue.svg"></a>
|
||||
<a href="https://dev.azure.com/mholt-dev/CertMagic/_build"><img src="https://img.shields.io/azure-devops/build/mholt-dev/3511431f-630c-43ac-833f-be949b4f4ee7/3.svg?label=cross-platform%20tests"></a>
|
||||
<a href="https://github.com/caddyserver/certmagic/actions?query=workflow%3ATests"><img src="https://github.com/caddyserver/certmagic/workflows/Tests/badge.svg"></a>
|
||||
<a href="https://sourcegraph.com/github.com/caddyserver/certmagic?badge"><img src="https://sourcegraph.com/github.com/caddyserver/certmagic/-/badge.svg"></a>
|
||||
</p>
|
||||
|
||||
@ -90,8 +90,9 @@ CertMagic - Automatic HTTPS using Let's Encrypt
|
||||
- Robust retries for up to 30 days
|
||||
- Exponential backoff with carefully-tuned intervals
|
||||
- Retries with optional test/staging CA endpoint instead of production, to avoid rate limits
|
||||
- Over 50 DNS providers work out-of-the-box (powered by [lego](https://github.com/go-acme/lego)!)
|
||||
- Written in Go, a language with memory-safety guarantees
|
||||
- Powered by [ACMEz](https://github.com/mholt/acmez), _the_ premier ACME client library for Go
|
||||
- All [libdns](https://github.com/libdns) DNS providers work out-of-the-box
|
||||
- Pluggable storage implementations (default: file system)
|
||||
- Wildcard certificates
|
||||
- Automatic OCSP stapling ([done right](https://gist.github.com/sleevi/5efe9ef98961ecfb4da8#gistcomment-2336055)) [keeps your sites online!](https://twitter.com/caddyserver/status/1234874273724084226)
|
||||
@ -248,9 +249,9 @@ cache := certmagic.NewCache(certmagic.CacheOptions{
|
||||
// mind that this config value is used as a
|
||||
// template, and will be completed with any
|
||||
// defaults that are set in the Default config
|
||||
return certmagic.Config{
|
||||
return &certmagic.Config{
|
||||
// ...
|
||||
}), nil
|
||||
}, nil
|
||||
},
|
||||
...
|
||||
})
|
||||
@ -385,30 +386,21 @@ ln, err := tls.Listen("tcp", ":443", myTLSConfig)
|
||||
|
||||
The DNS challenge is perhaps the most useful challenge because it allows you to obtain certificates without your server needing to be publicly accessible on the Internet, and it's the only challenge by which Let's Encrypt will issue wildcard certificates.
|
||||
|
||||
This challenge works by setting a special record in the domain's zone. To do this automatically, your DNS provider needs to offer an API by which changes can be made to domain names, and the changes need to take effect immediately for best results. CertMagic supports [all of lego's DNS provider implementations](https://github.com/go-acme/lego/tree/master/providers/dns)! All of them clean up the temporary record after the challenge completes.
|
||||
This challenge works by setting a special record in the domain's zone. To do this automatically, your DNS provider needs to offer an API by which changes can be made to domain names, and the changes need to take effect immediately for best results. CertMagic supports [all DNS providers with `libdns` implementations](https://github.com/libdns)! It always cleans up the temporary record after the challenge completes.
|
||||
|
||||
To enable it, just set the `DNSProvider` field on a `certmagic.Config` struct, or set the default `certmagic.DNSProvider` variable. For example, if my domains' DNS was served by DNSimple and I set my DNSimple API credentials in environment variables:
|
||||
To enable it, just set the `DNS01Solver` field on a `certmagic.ACMEManager` struct, or set the default `certmagic.ACMEManager.DNS01Solver` variable. For example, if my domains' DNS was served by Cloudflare:
|
||||
|
||||
```go
|
||||
import "github.com/go-acme/lego/v3/providers/dns/dnsimple"
|
||||
import "github.com/libdns/cloudflare"
|
||||
|
||||
provider, err := dnsimple.NewDNSProvider()
|
||||
if err != nil {
|
||||
return err
|
||||
certmagic.DefaultACME.DNS01Solver = &certmagic.DNS01Solver{
|
||||
DNSProvider: cloudflare.Provider{
|
||||
APIToken: "topsecret",
|
||||
},
|
||||
}
|
||||
|
||||
certmagic.DefaultACME.DNSProvider = provider
|
||||
```
|
||||
|
||||
Now the DNS challenge will be used by default, and I can obtain certificates for wildcard domains. See the [pkg.go.dev documentation for the provider you're using](https://pkg.go.dev/github.com/go-acme/lego/providers/dns?tab=subdirectories) to learn how to configure it. Most can be configured by env variables or by passing in a config struct. If you pass a config struct instead of using env variables, you will probably need to set some other defaults (that's just how lego works, currently):
|
||||
|
||||
```go
|
||||
PropagationTimeout: dns01.DefaultPollingInterval,
|
||||
PollingInterval: dns01.DefaultPollingInterval,
|
||||
TTL: dns01.DefaultTTL,
|
||||
```
|
||||
|
||||
Enabling the DNS challenge disables the other challenges for that `certmagic.Config` instance.
|
||||
Now the DNS challenge will be used by default, and I can obtain certificates for wildcard domains, too. Enabling the DNS challenge disables the other challenges for that `certmagic.ACMEManager` instance.
|
||||
|
||||
|
||||
## On-Demand TLS
|
||||
@ -503,7 +495,7 @@ We welcome your contributions! Please see our **[contributing guidelines](https:
|
||||
|
||||
## Project History
|
||||
|
||||
CertMagic is the core of Caddy's advanced TLS automation code, extracted into a library. The underlying ACME client implementation is [lego](https://github.com/go-acme/lego), which was originally developed for use in Caddy even before Let's Encrypt entered public beta in 2015.
|
||||
CertMagic is the core of Caddy's advanced TLS automation code, extracted into a library. The underlying ACME client implementation is [ACMEz](https://github.com/mholt/acmez). CertMagic's code was originally a central part of Caddy even before Let's Encrypt entered public beta in 2015.
|
||||
|
||||
In the years since then, Caddy's TLS automation techniques have been widely adopted, tried and tested in production, and served millions of sites and secured trillions of connections.
|
||||
|
@ -16,58 +16,138 @@ package certmagic
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"crypto"
|
||||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
"crypto/rand"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"path"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/go-acme/lego/v3/acme"
|
||||
"github.com/go-acme/lego/v3/registration"
|
||||
"github.com/mholt/acmez/acme"
|
||||
)
|
||||
|
||||
// user represents a Let's Encrypt user account.
|
||||
type user struct {
|
||||
Email string
|
||||
Registration *registration.Resource
|
||||
key crypto.PrivateKey
|
||||
}
|
||||
|
||||
// GetEmail gets u's email.
|
||||
func (u user) GetEmail() string {
|
||||
return u.Email
|
||||
}
|
||||
|
||||
// GetRegistration gets u's registration resource.
|
||||
func (u user) GetRegistration() *registration.Resource {
|
||||
return u.Registration
|
||||
}
|
||||
|
||||
// GetPrivateKey gets u's private key.
|
||||
func (u user) GetPrivateKey() crypto.PrivateKey {
|
||||
return u.key
|
||||
}
|
||||
|
||||
// newUser creates a new User for the given email address
|
||||
// with a new private key. This function does NOT save the
|
||||
// user to disk or register it via ACME. If you want to use
|
||||
// a user account that might already exist, call getUser
|
||||
// instead. It does NOT prompt the user.
|
||||
func (*ACMEManager) newUser(email string) (*user, error) {
|
||||
user := &user{Email: email}
|
||||
privateKey, err := ecdsa.GenerateKey(elliptic.P384(), rand.Reader)
|
||||
// getAccount either loads or creates a new account, depending on if
|
||||
// an account can be found in storage for the given CA + email combo.
|
||||
func (am *ACMEManager) getAccount(ca, email string) (acme.Account, error) {
|
||||
regBytes, err := am.config.Storage.Load(am.storageKeyUserReg(ca, email))
|
||||
if err != nil {
|
||||
return user, fmt.Errorf("generating private key: %v", err)
|
||||
if _, ok := err.(ErrNotExist); ok {
|
||||
return am.newAccount(email)
|
||||
}
|
||||
return acme.Account{}, err
|
||||
}
|
||||
user.key = privateKey
|
||||
return user, nil
|
||||
keyBytes, err := am.config.Storage.Load(am.storageKeyUserPrivateKey(ca, email))
|
||||
if err != nil {
|
||||
if _, ok := err.(ErrNotExist); ok {
|
||||
return am.newAccount(email)
|
||||
}
|
||||
return acme.Account{}, err
|
||||
}
|
||||
|
||||
var acct acme.Account
|
||||
err = json.Unmarshal(regBytes, &acct)
|
||||
if err != nil {
|
||||
return acct, err
|
||||
}
|
||||
acct.PrivateKey, err = decodePrivateKey(keyBytes)
|
||||
if err != nil {
|
||||
return acct, fmt.Errorf("could not decode account's private key: %v", err)
|
||||
}
|
||||
|
||||
// TODO: July 2020 - transition to new ACME lib and account structure;
|
||||
// for a while, we will need to convert old accounts to new structure
|
||||
acct, err = am.transitionAccountToACMEzJuly2020Format(ca, acct, regBytes)
|
||||
if err != nil {
|
||||
return acct, fmt.Errorf("one-time account transition: %v", err)
|
||||
}
|
||||
|
||||
return acct, err
|
||||
}
|
||||
|
||||
// TODO: this is a temporary transition helper starting July 2020.
|
||||
// It can go away when we think enough time has passed that most active assets have transitioned.
|
||||
func (am *ACMEManager) transitionAccountToACMEzJuly2020Format(ca string, acct acme.Account, regBytes []byte) (acme.Account, error) {
|
||||
if acct.Status != "" && acct.Location != "" {
|
||||
return acct, nil
|
||||
}
|
||||
|
||||
var oldAcct struct {
|
||||
Email string `json:"Email"`
|
||||
Registration struct {
|
||||
Body struct {
|
||||
Status string `json:"status"`
|
||||
TermsOfServiceAgreed bool `json:"termsOfServiceAgreed"`
|
||||
Orders string `json:"orders"`
|
||||
ExternalAccountBinding json.RawMessage `json:"externalAccountBinding"`
|
||||
} `json:"body"`
|
||||
URI string `json:"uri"`
|
||||
} `json:"Registration"`
|
||||
}
|
||||
err := json.Unmarshal(regBytes, &oldAcct)
|
||||
if err != nil {
|
||||
return acct, fmt.Errorf("decoding into old account type: %v", err)
|
||||
}
|
||||
|
||||
acct.Status = oldAcct.Registration.Body.Status
|
||||
acct.TermsOfServiceAgreed = oldAcct.Registration.Body.TermsOfServiceAgreed
|
||||
acct.Location = oldAcct.Registration.URI
|
||||
acct.ExternalAccountBinding = oldAcct.Registration.Body.ExternalAccountBinding
|
||||
acct.Orders = oldAcct.Registration.Body.Orders
|
||||
if oldAcct.Email != "" {
|
||||
acct.Contact = []string{"mailto:" + oldAcct.Email}
|
||||
}
|
||||
|
||||
err = am.saveAccount(ca, acct)
|
||||
if err != nil {
|
||||
return acct, fmt.Errorf("saving converted account: %v", err)
|
||||
}
|
||||
|
||||
return acct, nil
|
||||
}
|
||||
|
||||
// newAccount generates a new private key for a new ACME account, but
|
||||
// it does not register or save the account.
|
||||
func (*ACMEManager) newAccount(email string) (acme.Account, error) {
|
||||
var acct acme.Account
|
||||
if email != "" {
|
||||
acct.Contact = []string{"mailto:" + email} // TODO: should we abstract the contact scheme?
|
||||
}
|
||||
privateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||
if err != nil {
|
||||
return acct, fmt.Errorf("generating private key: %v", err)
|
||||
}
|
||||
acct.PrivateKey = privateKey
|
||||
return acct, nil
|
||||
}
|
||||
|
||||
// saveAccount persists an ACME account's info and private key to storage.
|
||||
// It does NOT register the account via ACME or prompt the user.
|
||||
func (am *ACMEManager) saveAccount(ca string, account acme.Account) error {
|
||||
regBytes, err := json.MarshalIndent(account, "", "\t")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
keyBytes, err := encodePrivateKey(account.PrivateKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// extract primary contact (email), without scheme (e.g. "mailto:")
|
||||
primaryContact := getPrimaryContact(account)
|
||||
all := []keyValue{
|
||||
{
|
||||
key: am.storageKeyUserReg(ca, primaryContact),
|
||||
value: regBytes,
|
||||
},
|
||||
{
|
||||
key: am.storageKeyUserPrivateKey(ca, primaryContact),
|
||||
value: keyBytes,
|
||||
},
|
||||
}
|
||||
return storeTx(am.config.Storage, all)
|
||||
}
|
||||
|
||||
// getEmail does everything it can to obtain an email address
|
||||
@ -82,13 +162,13 @@ func (am *ACMEManager) getEmail(allowPrompts bool) error {
|
||||
|
||||
// First try package default email
|
||||
if leEmail == "" {
|
||||
leEmail = DefaultACME.Email // TODO: racey with line 108
|
||||
leEmail = DefaultACME.Email // TODO: racey with line 122 (or whichever line assigns to DefaultACME.Email below)
|
||||
}
|
||||
|
||||
// Then try to get most recent user email from storage
|
||||
var gotRecentEmail bool
|
||||
if leEmail == "" {
|
||||
leEmail, gotRecentEmail = am.mostRecentUserEmail(am.CA)
|
||||
leEmail, gotRecentEmail = am.mostRecentAccountEmail(am.CA)
|
||||
}
|
||||
if !gotRecentEmail && leEmail == "" && allowPrompts {
|
||||
// Looks like there is no email address readily available,
|
||||
@ -105,46 +185,21 @@ func (am *ACMEManager) getEmail(allowPrompts bool) error {
|
||||
|
||||
// save the email for later and ensure it is consistent
|
||||
// for repeated use; then update cfg with the email
|
||||
DefaultACME.Email = strings.TrimSpace(strings.ToLower(leEmail)) // TODO: this is racey with line 85
|
||||
DefaultACME.Email = strings.TrimSpace(strings.ToLower(leEmail)) // TODO: this is racey with line 99
|
||||
am.Email = DefaultACME.Email
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (am *ACMEManager) getAgreementURL() (string, error) {
|
||||
if agreementTestURL != "" {
|
||||
return agreementTestURL, nil
|
||||
}
|
||||
caURL := am.CA
|
||||
if caURL == "" {
|
||||
caURL = DefaultACME.CA
|
||||
}
|
||||
response, err := http.Get(caURL)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer response.Body.Close()
|
||||
var dir acme.Directory
|
||||
err = json.NewDecoder(response.Body).Decode(&dir)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return dir.Meta.TermsOfService, nil
|
||||
}
|
||||
|
||||
// promptUserForEmail prompts the user for an email address
|
||||
// and returns the email address they entered (which could
|
||||
// be the empty string). If no error is returned, then Agreed
|
||||
// will also be set to true, since continuing through the
|
||||
// prompt signifies agreement.
|
||||
func (am *ACMEManager) promptUserForEmail() (string, error) {
|
||||
agreementURL, err := am.getAgreementURL()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("get Agreement URL: %v", err)
|
||||
}
|
||||
// prompt the user for an email address and terms agreement
|
||||
reader := bufio.NewReader(stdin)
|
||||
am.promptUserAgreement(agreementURL)
|
||||
am.promptUserAgreement("")
|
||||
fmt.Println("Please enter your email address to signify agreement and to be notified")
|
||||
fmt.Println("in case of issues. You can leave it blank, but we don't recommend it.")
|
||||
fmt.Print(" Email address: ")
|
||||
@ -157,72 +212,17 @@ func (am *ACMEManager) promptUserForEmail() (string, error) {
|
||||
return leEmail, nil
|
||||
}
|
||||
|
||||
// getUser loads the user with the given email from disk
|
||||
// using the provided storage. If the user does not exist,
|
||||
// it will create a new one, but it does NOT save new
|
||||
// users to the disk or register them via ACME. It does
|
||||
// NOT prompt the user.
|
||||
func (am *ACMEManager) getUser(ca, email string) (*user, error) {
|
||||
regBytes, err := am.config.Storage.Load(am.storageKeyUserReg(ca, email))
|
||||
if err != nil {
|
||||
if _, ok := err.(ErrNotExist); ok {
|
||||
// create a new user
|
||||
return am.newUser(email)
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
keyBytes, err := am.config.Storage.Load(am.storageKeyUserPrivateKey(ca, email))
|
||||
if err != nil {
|
||||
if _, ok := err.(ErrNotExist); ok {
|
||||
// create a new user
|
||||
return am.newUser(email)
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var u *user
|
||||
err = json.Unmarshal(regBytes, &u)
|
||||
if err != nil {
|
||||
return u, err
|
||||
}
|
||||
u.key, err = decodePrivateKey(keyBytes)
|
||||
return u, err
|
||||
}
|
||||
|
||||
// saveUser persists a user's key and account registration
|
||||
// to the file system. It does NOT register the user via ACME
|
||||
// or prompt the user. You must also pass in the storage
|
||||
// wherein the user should be saved. It should be the storage
|
||||
// for the CA with which user has an account.
|
||||
func (am *ACMEManager) saveUser(ca string, user *user) error {
|
||||
regBytes, err := json.MarshalIndent(&user, "", "\t")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
keyBytes, err := encodePrivateKey(user.key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
all := []keyValue{
|
||||
{
|
||||
key: am.storageKeyUserReg(ca, user.Email),
|
||||
value: regBytes,
|
||||
},
|
||||
{
|
||||
key: am.storageKeyUserPrivateKey(ca, user.Email),
|
||||
value: keyBytes,
|
||||
},
|
||||
}
|
||||
return storeTx(am.config.Storage, all)
|
||||
}
|
||||
|
||||
// promptUserAgreement simply outputs the standard user
|
||||
// agreement prompt with the given agreement URL.
|
||||
// It outputs a newline after the message.
|
||||
func (am *ACMEManager) promptUserAgreement(agreementURL string) {
|
||||
const userAgreementPrompt = `Your sites will be served over HTTPS automatically using Let's Encrypt.
|
||||
By continuing, you agree to the Let's Encrypt Subscriber Agreement at:`
|
||||
fmt.Printf("\n\n%s\n %s\n", userAgreementPrompt, agreementURL)
|
||||
userAgreementPrompt := `Your sites will be served over HTTPS automatically using an automated CA.
|
||||
By continuing, you agree to the CA's terms of service`
|
||||
if agreementURL == "" {
|
||||
fmt.Printf("\n\n%s.\n", userAgreementPrompt)
|
||||
return
|
||||
}
|
||||
fmt.Printf("\n\n%s at:\n %s\n", userAgreementPrompt, agreementURL)
|
||||
}
|
||||
|
||||
// askUserAgreement prompts the user to agree to the agreement
|
||||
@ -292,20 +292,20 @@ func (*ACMEManager) emailUsername(email string) string {
|
||||
return email[:at]
|
||||
}
|
||||
|
||||
// mostRecentUserEmail finds the most recently-written user file
|
||||
// mostRecentAccountEmail finds the most recently-written account file
|
||||
// in storage. Since this is part of a complex sequence to get a user
|
||||
// account, errors here are discarded to simplify code flow in
|
||||
// the caller, and errors are not important here anyway.
|
||||
func (am *ACMEManager) mostRecentUserEmail(caURL string) (string, bool) {
|
||||
userList, err := am.config.Storage.List(am.storageKeyUsersPrefix(caURL), false)
|
||||
if err != nil || len(userList) == 0 {
|
||||
func (am *ACMEManager) mostRecentAccountEmail(caURL string) (string, bool) {
|
||||
accountList, err := am.config.Storage.List(am.storageKeyUsersPrefix(caURL), false)
|
||||
if err != nil || len(accountList) == 0 {
|
||||
return "", false
|
||||
}
|
||||
|
||||
// get all the key infos ahead of sorting, because
|
||||
// we might filter some out
|
||||
stats := make(map[string]KeyInfo)
|
||||
for i, u := range userList {
|
||||
for i, u := range accountList {
|
||||
keyInfo, err := am.config.Storage.Stat(u)
|
||||
if err != nil {
|
||||
continue
|
||||
@ -317,24 +317,42 @@ func (am *ACMEManager) mostRecentUserEmail(caURL string) (string, bool) {
|
||||
// which existed... sure, this isn't a perfect fix but
|
||||
// frankly one's OS shouldn't mess with the data folder
|
||||
// in the first place.
|
||||
userList = append(userList[:i], userList[i+1:]...)
|
||||
accountList = append(accountList[:i], accountList[i+1:]...)
|
||||
continue
|
||||
}
|
||||
stats[u] = keyInfo
|
||||
}
|
||||
|
||||
sort.Slice(userList, func(i, j int) bool {
|
||||
iInfo := stats[userList[i]]
|
||||
jInfo := stats[userList[j]]
|
||||
sort.Slice(accountList, func(i, j int) bool {
|
||||
iInfo := stats[accountList[i]]
|
||||
jInfo := stats[accountList[j]]
|
||||
return jInfo.Modified.Before(iInfo.Modified)
|
||||
})
|
||||
|
||||
user, err := am.getUser(caURL, path.Base(userList[0]))
|
||||
if len(accountList) == 0 {
|
||||
return "", false
|
||||
}
|
||||
|
||||
account, err := am.getAccount(caURL, path.Base(accountList[0]))
|
||||
if err != nil {
|
||||
return "", false
|
||||
}
|
||||
|
||||
return user.Email, true
|
||||
return getPrimaryContact(account), true
|
||||
}
|
||||
|
||||
// getPrimaryContact returns the first contact on the account (if any)
|
||||
// without the scheme. (I guess we assume an email address.)
|
||||
func getPrimaryContact(account acme.Account) string {
|
||||
// TODO: should this be abstracted with some lower-level helper?
|
||||
var primaryContact string
|
||||
if len(account.Contact) > 0 {
|
||||
primaryContact = account.Contact[0]
|
||||
if idx := strings.Index(primaryContact, ":"); idx >= 0 {
|
||||
primaryContact = primaryContact[idx+1:]
|
||||
}
|
||||
}
|
||||
return primaryContact
|
||||
}
|
||||
|
||||
// agreementTestURL is set during tests to skip requiring
|
340
vendor/github.com/coolaj86/certmagic/acmeclient.go
generated
vendored
Normal file
340
vendor/github.com/coolaj86/certmagic/acmeclient.go
generated
vendored
Normal file
@ -0,0 +1,340 @@
|
||||
// Copyright 2015 Matthew Holt
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package certmagic
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"fmt"
|
||||
weakrand "math/rand"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/mholt/acmez"
|
||||
"github.com/mholt/acmez/acme"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
func init() {
|
||||
weakrand.Seed(time.Now().UnixNano())
|
||||
}
|
||||
|
||||
// acmeClient holds state necessary for us to perform
|
||||
// ACME operations for certificate management. Call
|
||||
// ACMEManager.newACMEClient() to get a valid one to .
|
||||
type acmeClient struct {
|
||||
mgr *ACMEManager
|
||||
acmeClient *acmez.Client
|
||||
account acme.Account
|
||||
}
|
||||
|
||||
// newACMEClient creates the underlying ACME library client type.
|
||||
// If useTestCA is true, am.TestCA will be used if it is set;
|
||||
// otherwise, the primary CA will still be used.
|
||||
func (am *ACMEManager) newACMEClient(ctx context.Context, useTestCA, interactive bool) (*acmeClient, error) {
|
||||
// ensure defaults are filled in
|
||||
var caURL string
|
||||
if useTestCA {
|
||||
caURL = am.TestCA
|
||||
}
|
||||
if caURL == "" {
|
||||
caURL = am.CA
|
||||
}
|
||||
if caURL == "" {
|
||||
caURL = DefaultACME.CA
|
||||
}
|
||||
certObtainTimeout := am.CertObtainTimeout
|
||||
if certObtainTimeout == 0 {
|
||||
certObtainTimeout = DefaultACME.CertObtainTimeout
|
||||
}
|
||||
|
||||
// ensure endpoint is secure (assume HTTPS if scheme is missing)
|
||||
if !strings.Contains(caURL, "://") {
|
||||
caURL = "https://" + caURL
|
||||
}
|
||||
u, err := url.Parse(caURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if u.Scheme != "https" && !isLoopback(u.Host) && !isInternal(u.Host) {
|
||||
return nil, fmt.Errorf("%s: insecure CA URL (HTTPS required)", caURL)
|
||||
}
|
||||
|
||||
// look up or create the ACME account
|
||||
account, err := am.getAccount(caURL, am.Email)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("getting ACME account: %v", err)
|
||||
}
|
||||
|
||||
// set up the dialers and resolver for the ACME client's HTTP client
|
||||
dialer := &net.Dialer{
|
||||
Timeout: 30 * time.Second,
|
||||
KeepAlive: 2 * time.Minute,
|
||||
}
|
||||
if am.Resolver != "" {
|
||||
dialer.Resolver = &net.Resolver{
|
||||
PreferGo: true,
|
||||
Dial: func(ctx context.Context, network, _ string) (net.Conn, error) {
|
||||
return (&net.Dialer{
|
||||
Timeout: 15 * time.Second,
|
||||
}).DialContext(ctx, network, am.Resolver)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: we could potentially reuse the HTTP transport and client
|
||||
hc := am.httpClient // TODO: is this racey?
|
||||
if am.httpClient == nil {
|
||||
transport := &http.Transport{
|
||||
Proxy: http.ProxyFromEnvironment,
|
||||
DialContext: dialer.DialContext,
|
||||
TLSHandshakeTimeout: 15 * time.Second,
|
||||
ResponseHeaderTimeout: 15 * time.Second,
|
||||
ExpectContinueTimeout: 2 * time.Second,
|
||||
ForceAttemptHTTP2: true,
|
||||
}
|
||||
if am.TrustedRoots != nil {
|
||||
transport.TLSClientConfig = &tls.Config{
|
||||
RootCAs: am.TrustedRoots,
|
||||
}
|
||||
}
|
||||
|
||||
hc = &http.Client{
|
||||
Transport: transport,
|
||||
Timeout: HTTPTimeout,
|
||||
}
|
||||
|
||||
am.httpClient = hc
|
||||
}
|
||||
|
||||
client := &acmez.Client{
|
||||
Client: &acme.Client{
|
||||
Directory: caURL,
|
||||
PollTimeout: certObtainTimeout,
|
||||
UserAgent: buildUAString(),
|
||||
HTTPClient: hc,
|
||||
},
|
||||
ChallengeSolvers: make(map[string]acmez.Solver),
|
||||
}
|
||||
if am.Logger != nil {
|
||||
l := am.Logger.Named("acme_client")
|
||||
client.Client.Logger, client.Logger = l, l
|
||||
}
|
||||
|
||||
// configure challenges (most of the time, DNS challenge is
|
||||
// exclusive of other ones because it is usually only used
|
||||
// in situations where the default challenges would fail)
|
||||
if am.DNS01Solver == nil {
|
||||
// enable HTTP-01 challenge
|
||||
if !am.DisableHTTPChallenge && am.HTTP01Solver == nil {
|
||||
useHTTPPort := HTTPChallengePort
|
||||
if HTTPPort > 0 && HTTPPort != HTTPChallengePort {
|
||||
useHTTPPort = HTTPPort
|
||||
}
|
||||
if am.AltHTTPPort > 0 {
|
||||
useHTTPPort = am.AltHTTPPort
|
||||
}
|
||||
am.HTTP01Solver = distributedSolver{
|
||||
acmeManager: am,
|
||||
solver: &httpSolver{
|
||||
acmeManager: am,
|
||||
address: net.JoinHostPort(am.ListenHost, strconv.Itoa(useHTTPPort)),
|
||||
},
|
||||
caURL: client.Directory,
|
||||
}
|
||||
}
|
||||
client.ChallengeSolvers[acme.ChallengeTypeHTTP01] = am.HTTP01Solver
|
||||
|
||||
// enable TLS-ALPN-01 challenge
|
||||
if !am.DisableTLSALPNChallenge {
|
||||
useTLSALPNPort := TLSALPNChallengePort
|
||||
if HTTPSPort > 0 && HTTPSPort != TLSALPNChallengePort {
|
||||
useTLSALPNPort = HTTPSPort
|
||||
}
|
||||
if am.AltTLSALPNPort > 0 {
|
||||
useTLSALPNPort = am.AltTLSALPNPort
|
||||
}
|
||||
client.ChallengeSolvers[acme.ChallengeTypeTLSALPN01] = distributedSolver{
|
||||
acmeManager: am,
|
||||
solver: &tlsALPNSolver{
|
||||
config: am.config,
|
||||
address: net.JoinHostPort(am.ListenHost, strconv.Itoa(useTLSALPNPort)),
|
||||
},
|
||||
caURL: client.Directory,
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// use DNS challenge exclusively
|
||||
client.ChallengeSolvers[acme.ChallengeTypeDNS01] = am.DNS01Solver
|
||||
}
|
||||
|
||||
// register account if it is new
|
||||
if account.Status == "" {
|
||||
if am.NewAccountFunc != nil {
|
||||
err = am.NewAccountFunc(ctx, am, account)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("account pre-registration callback: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// agree to terms
|
||||
if interactive {
|
||||
if !am.Agreed {
|
||||
var termsURL string
|
||||
dir, err := client.GetDirectory(ctx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("getting directory: %w", err)
|
||||
}
|
||||
if dir.Meta != nil {
|
||||
termsURL = dir.Meta.TermsOfService
|
||||
}
|
||||
if termsURL != "" {
|
||||
am.Agreed = am.askUserAgreement(termsURL)
|
||||
if !am.Agreed {
|
||||
return nil, fmt.Errorf("user must agree to CA terms")
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// can't prompt a user who isn't there; they should
|
||||
// have reviewed the terms beforehand
|
||||
am.Agreed = true
|
||||
}
|
||||
account.TermsOfServiceAgreed = am.Agreed
|
||||
|
||||
// associate account with external binding, if configured
|
||||
if am.ExternalAccount != nil {
|
||||
err := account.SetExternalAccountBinding(ctx, client.Client, *am.ExternalAccount)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// create account
|
||||
account, err = client.NewAccount(ctx, account)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("registering account with server: %w", err)
|
||||
}
|
||||
|
||||
// persist the account to storage
|
||||
err = am.saveAccount(caURL, account)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not save account: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
c := &acmeClient{
|
||||
mgr: am,
|
||||
acmeClient: client,
|
||||
account: account,
|
||||
}
|
||||
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func (c *acmeClient) throttle(ctx context.Context, names []string) error {
|
||||
// throttling is scoped to CA + account email
|
||||
rateLimiterKey := c.acmeClient.Directory + "," + c.mgr.Email
|
||||
rateLimitersMu.Lock()
|
||||
rl, ok := rateLimiters[rateLimiterKey]
|
||||
if !ok {
|
||||
rl = NewRateLimiter(RateLimitEvents, RateLimitEventsWindow)
|
||||
rateLimiters[rateLimiterKey] = rl
|
||||
// TODO: stop rate limiter when it is garbage-collected...
|
||||
}
|
||||
rateLimitersMu.Unlock()
|
||||
if c.mgr.Logger != nil {
|
||||
c.mgr.Logger.Info("waiting on internal rate limiter", zap.Strings("identifiers", names))
|
||||
}
|
||||
err := rl.Wait(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if c.mgr.Logger != nil {
|
||||
c.mgr.Logger.Info("done waiting on internal rate limiter", zap.Strings("identifiers", names))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *acmeClient) usingTestCA() bool {
|
||||
return c.mgr.TestCA != "" && c.acmeClient.Directory == c.mgr.TestCA
|
||||
}
|
||||
|
||||
func (c *acmeClient) revoke(ctx context.Context, cert *x509.Certificate, reason int) error {
|
||||
return c.acmeClient.RevokeCertificate(ctx, c.account,
|
||||
cert, c.account.PrivateKey, reason)
|
||||
}
|
||||
|
||||
func buildUAString() string {
|
||||
ua := "CertMagic"
|
||||
if UserAgent != "" {
|
||||
ua = UserAgent + " " + ua
|
||||
}
|
||||
return ua
|
||||
}
|
||||
|
||||
// These internal rate limits are designed to prevent accidentally
|
||||
// firehosing a CA's ACME endpoints. They are not intended to
|
||||
// replace or replicate the CA's actual rate limits.
|
||||
//
|
||||
// Let's Encrypt's rate limits can be found here:
|
||||
// https://letsencrypt.org/docs/rate-limits/
|
||||
//
|
||||
// Currently (as of December 2019), Let's Encrypt's most relevant
|
||||
// rate limit for large deployments is 300 new orders per account
|
||||
// per 3 hours (on average, or best case, that's about 1 every 36
|
||||
// seconds, or 2 every 72 seconds, etc.); but it's not reasonable
|
||||
// to try to assume that our internal state is the same as the CA's
|
||||
// (due to process restarts, config changes, failed validations,
|
||||
// etc.) and ultimately, only the CA's actual rate limiter is the
|
||||
// authority. Thus, our own rate limiters do not attempt to enforce
|
||||
// external rate limits. Doing so causes problems when the domains
|
||||
// are not in our control (i.e. serving customer sites) and/or lots
|
||||
// of domains fail validation: they clog our internal rate limiter
|
||||
// and nearly starve out (or at least slow down) the other domains
|
||||
// that need certificates. Failed transactions are already retried
|
||||
// with exponential backoff, so adding in rate limiting can slow
|
||||
// things down even more.
|
||||
//
|
||||
// Instead, the point of our internal rate limiter is to avoid
|
||||
// hammering the CA's endpoint when there are thousands or even
|
||||
// millions of certificates under management. Our goal is to
|
||||
// allow small bursts in a relatively short timeframe so as to
|
||||
// not block any one domain for too long, without unleashing
|
||||
// thousands of requests to the CA at once.
|
||||
var (
|
||||
rateLimiters = make(map[string]*RingBufferRateLimiter)
|
||||
rateLimitersMu sync.RWMutex
|
||||
|
||||
// RateLimitEvents is how many new events can be allowed
|
||||
// in RateLimitEventsWindow.
|
||||
RateLimitEvents = 10
|
||||
|
||||
// RateLimitEventsWindow is the size of the sliding
|
||||
// window that throttles events.
|
||||
RateLimitEventsWindow = 1 * time.Minute
|
||||
)
|
||||
|
||||
// Some default values passed down to the underlying ACME client.
|
||||
var (
|
||||
UserAgent string
|
||||
HTTPTimeout = 30 * time.Second
|
||||
)
|
@ -5,16 +5,14 @@ import (
|
||||
"crypto/x509"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/go-acme/lego/v3/acme"
|
||||
"github.com/go-acme/lego/v3/certificate"
|
||||
"github.com/go-acme/lego/v3/challenge"
|
||||
"github.com/go-acme/lego/v3/challenge/dns01"
|
||||
"github.com/mholt/acmez"
|
||||
"github.com/mholt/acmez/acme"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
// ACMEManager gets certificates using ACME. It implements the PreChecker,
|
||||
@ -45,7 +43,7 @@ type ACMEManager struct {
|
||||
|
||||
// An optional external account to associate
|
||||
// with this ACME account
|
||||
ExternalAccount *ExternalAccountBinding
|
||||
ExternalAccount *acme.EAB
|
||||
|
||||
// Disable all HTTP challenges
|
||||
DisableHTTPChallenge bool
|
||||
@ -70,14 +68,13 @@ type ACMEManager struct {
|
||||
// challenge to succeed
|
||||
AltTLSALPNPort int
|
||||
|
||||
// The DNS provider to use when solving the
|
||||
// ACME DNS challenge
|
||||
DNSProvider challenge.Provider
|
||||
// The solver for the dns-01 challenge;
|
||||
// usually this is a DNS01Solver value
|
||||
// from this package
|
||||
DNS01Solver acmez.Solver
|
||||
|
||||
// The ChallengeOption struct to provide
|
||||
// custom precheck or name resolution options
|
||||
// for DNS challenge validation and execution
|
||||
DNSChallengeOption dns01.ChallengeOption
|
||||
// The solver for the http-01 challenge
|
||||
HTTP01Solver acmez.Solver
|
||||
|
||||
// TrustedRoots specifies a pool of root CA
|
||||
// certificates to trust when communicating
|
||||
@ -86,13 +83,27 @@ type ACMEManager struct {
|
||||
|
||||
// The maximum amount of time to allow for
|
||||
// obtaining a certificate. If empty, the
|
||||
// default from the underlying lego lib is
|
||||
// default from the underlying ACME lib is
|
||||
// used. If set, it must not be too low so
|
||||
// as to cancel orders too early, running
|
||||
// the risk of rate limiting.
|
||||
// as to cancel challenges too early.
|
||||
CertObtainTimeout time.Duration
|
||||
|
||||
config *Config
|
||||
// Address of custom DNS resolver to be used
|
||||
// when communicating with ACME server
|
||||
Resolver string
|
||||
|
||||
// Callback function that is called before a
|
||||
// new ACME account is registered with the CA;
|
||||
// it allows for last-second config changes
|
||||
// of the ACMEManager (TODO: this feature is
|
||||
// still EXPERIMENTAL and subject to change)
|
||||
NewAccountFunc func(context.Context, *ACMEManager, acme.Account) error
|
||||
|
||||
// Set a logger to enable logging
|
||||
Logger *zap.Logger
|
||||
|
||||
config *Config
|
||||
httpClient *http.Client
|
||||
}
|
||||
|
||||
// NewACMEManager constructs a valid ACMEManager based on a template
|
||||
@ -108,7 +119,11 @@ func NewACMEManager(cfg *Config, template ACMEManager) *ACMEManager {
|
||||
if template.CA == "" {
|
||||
template.CA = DefaultACME.CA
|
||||
}
|
||||
if template.TestCA == "" {
|
||||
if template.TestCA == "" && template.CA == DefaultACME.CA {
|
||||
// only use the default test CA if the CA is also
|
||||
// the default CA; no point in testing against
|
||||
// Let's Encrypt's staging server if we are not
|
||||
// using their production server too
|
||||
template.TestCA = DefaultACME.TestCA
|
||||
}
|
||||
if template.Email == "" {
|
||||
@ -117,6 +132,9 @@ func NewACMEManager(cfg *Config, template ACMEManager) *ACMEManager {
|
||||
if !template.Agreed {
|
||||
template.Agreed = DefaultACME.Agreed
|
||||
}
|
||||
if template.ExternalAccount == nil {
|
||||
template.ExternalAccount = DefaultACME.ExternalAccount
|
||||
}
|
||||
if !template.DisableHTTPChallenge {
|
||||
template.DisableHTTPChallenge = DefaultACME.DisableHTTPChallenge
|
||||
}
|
||||
@ -132,11 +150,8 @@ func NewACMEManager(cfg *Config, template ACMEManager) *ACMEManager {
|
||||
if template.AltTLSALPNPort == 0 {
|
||||
template.AltTLSALPNPort = DefaultACME.AltTLSALPNPort
|
||||
}
|
||||
if template.DNSProvider == nil {
|
||||
template.DNSProvider = DefaultACME.DNSProvider
|
||||
}
|
||||
if template.DNSChallengeOption == nil {
|
||||
template.DNSChallengeOption = DefaultACME.DNSChallengeOption
|
||||
if template.DNS01Solver == nil {
|
||||
template.DNS01Solver = DefaultACME.DNS01Solver
|
||||
}
|
||||
if template.TrustedRoots == nil {
|
||||
template.TrustedRoots = DefaultACME.TrustedRoots
|
||||
@ -144,6 +159,15 @@ func NewACMEManager(cfg *Config, template ACMEManager) *ACMEManager {
|
||||
if template.CertObtainTimeout == 0 {
|
||||
template.CertObtainTimeout = DefaultACME.CertObtainTimeout
|
||||
}
|
||||
if template.Resolver == "" {
|
||||
template.Resolver = DefaultACME.Resolver
|
||||
}
|
||||
if template.NewAccountFunc == nil {
|
||||
template.NewAccountFunc = DefaultACME.NewAccountFunc
|
||||
}
|
||||
if template.Logger == nil {
|
||||
template.Logger = DefaultACME.Logger
|
||||
}
|
||||
template.config = cfg
|
||||
return &template
|
||||
}
|
||||
@ -180,7 +204,7 @@ func (am *ACMEManager) issuerKey(ca string) string {
|
||||
// renewing a certificate with ACME, and returns whether this
|
||||
// batch is eligible for certificates if using Let's Encrypt.
|
||||
// It also ensures that an email address is available.
|
||||
func (am *ACMEManager) PreCheck(names []string, interactive bool) error {
|
||||
func (am *ACMEManager) PreCheck(_ context.Context, names []string, interactive bool) error {
|
||||
letsEncrypt := strings.Contains(am.CA, "api.letsencrypt.org")
|
||||
if letsEncrypt {
|
||||
for _, name := range names {
|
||||
@ -240,9 +264,9 @@ func (am *ACMEManager) Issue(ctx context.Context, csr *x509.CertificateRequest)
|
||||
// externally; it is hard to tell which! one easy cue is whether the
|
||||
// error is specifically a 429 (Too Many Requests); if so, we should
|
||||
// probably keep retrying
|
||||
var acmeErr acme.ProblemDetails
|
||||
if errors.As(err, &acmeErr) {
|
||||
if acmeErr.HTTPStatus == http.StatusTooManyRequests {
|
||||
var problem acme.Problem
|
||||
if errors.As(err, &problem) {
|
||||
if problem.Status == http.StatusTooManyRequests {
|
||||
// DON'T abort retries; the test CA succeeded (even
|
||||
// if it's cached, it recently succeeded!) so we just
|
||||
// need to keep trying (with backoff) until this CA's
|
||||
@ -261,7 +285,7 @@ func (am *ACMEManager) Issue(ctx context.Context, csr *x509.CertificateRequest)
|
||||
}
|
||||
|
||||
func (am *ACMEManager) doIssue(ctx context.Context, csr *x509.CertificateRequest, useTestCA bool) (*IssuedCertificate, bool, error) {
|
||||
client, err := am.newACMEClientWithRetry(useTestCA)
|
||||
client, err := am.newACMEClient(ctx, useTestCA, false)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
@ -275,64 +299,34 @@ func (am *ACMEManager) doIssue(ctx context.Context, csr *x509.CertificateRequest
|
||||
}
|
||||
}
|
||||
|
||||
certRes, err := client.tryAllEnabledChallenges(ctx, csr)
|
||||
certChains, err := client.acmeClient.ObtainCertificateUsingCSR(ctx, client.account, csr)
|
||||
if err != nil {
|
||||
return nil, usingTestCA, fmt.Errorf("%v %w", nameSet, err)
|
||||
return nil, usingTestCA, fmt.Errorf("%v %w (ca=%s)", nameSet, err, client.acmeClient.Directory)
|
||||
}
|
||||
|
||||
// TODO: ACME server could in theory issue a cert with multiple chains,
|
||||
// but we don't (yet) have a way to choose one, so just use first one
|
||||
ic := &IssuedCertificate{
|
||||
Certificate: certRes.Certificate,
|
||||
Metadata: certRes,
|
||||
Certificate: certChains[0].ChainPEM,
|
||||
Metadata: certChains[0],
|
||||
}
|
||||
|
||||
return ic, usingTestCA, nil
|
||||
}
|
||||
|
||||
func (c *acmeClient) tryAllEnabledChallenges(ctx context.Context, csr *x509.CertificateRequest) (*certificate.Resource, error) {
|
||||
// start with all enabled challenges
|
||||
challenges := c.initialChallenges()
|
||||
if len(challenges) == 0 {
|
||||
return nil, fmt.Errorf("no challenge types enabled")
|
||||
}
|
||||
|
||||
// try while a challenge type is still available
|
||||
var cert *certificate.Resource
|
||||
var err error
|
||||
for len(challenges) > 0 {
|
||||
var chosenChallenge challenge.Type
|
||||
chosenChallenge, challenges = c.nextChallenge(challenges)
|
||||
cert, err = c.acmeClient.Certificate.ObtainForCSR(*csr, true)
|
||||
if err == nil {
|
||||
return cert, nil
|
||||
}
|
||||
log.Printf("[ERROR] %s (challenge=%s remaining=%v)", err, chosenChallenge, challenges)
|
||||
time.Sleep(2 * time.Second)
|
||||
}
|
||||
return cert, err
|
||||
}
|
||||
|
||||
// Revoke implements the Revoker interface. It revokes the given certificate.
|
||||
func (am *ACMEManager) Revoke(ctx context.Context, cert CertificateResource) error {
|
||||
client, err := am.newACMEClient(false, false)
|
||||
func (am *ACMEManager) Revoke(ctx context.Context, cert CertificateResource, reason int) error {
|
||||
client, err := am.newACMEClient(ctx, false, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
meta := cert.IssuerData.(map[string]interface{})
|
||||
cr := certificate.Resource{
|
||||
Domain: meta["domain"].(string),
|
||||
CertURL: meta["certUrl"].(string),
|
||||
CertStableURL: meta["certStableURL"].(string),
|
||||
certs, err := parseCertsFromPEMBundle(cert.CertificatePEM)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return client.revoke(ctx, cr)
|
||||
}
|
||||
|
||||
// ExternalAccountBinding contains information for
|
||||
// binding an external account to an ACME account.
|
||||
type ExternalAccountBinding struct {
|
||||
KeyID string
|
||||
HMAC []byte
|
||||
return client.revoke(ctx, certs[0], reason)
|
||||
}
|
||||
|
||||
// DefaultACME specifies the default settings
|
@ -4,8 +4,11 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"log"
|
||||
"runtime"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
var jm = &jobManager{maxConcurrentJobs: 1000}
|
||||
@ -19,15 +22,16 @@ type jobManager struct {
|
||||
}
|
||||
|
||||
type namedJob struct {
|
||||
name string
|
||||
job func() error
|
||||
name string
|
||||
job func() error
|
||||
logger *zap.Logger
|
||||
}
|
||||
|
||||
// Submit enqueues the given job with the given name. If name is non-empty
|
||||
// and a job with the same name is already enqueued or running, this is a
|
||||
// no-op. If name is empty, no duplicate prevention will occur. The job
|
||||
// manager will then run this job as soon as it is able.
|
||||
func (jm *jobManager) Submit(name string, job func() error) {
|
||||
func (jm *jobManager) Submit(logger *zap.Logger, name string, job func() error) {
|
||||
jm.mu.Lock()
|
||||
defer jm.mu.Unlock()
|
||||
if jm.names == nil {
|
||||
@ -40,7 +44,7 @@ func (jm *jobManager) Submit(name string, job func() error) {
|
||||
}
|
||||
jm.names[name] = struct{}{}
|
||||
}
|
||||
jm.queue = append(jm.queue, namedJob{name, job})
|
||||
jm.queue = append(jm.queue, namedJob{name, job, logger})
|
||||
if jm.activeWorkers < jm.maxConcurrentJobs {
|
||||
jm.activeWorkers++
|
||||
go jm.worker()
|
||||
@ -48,6 +52,14 @@ func (jm *jobManager) Submit(name string, job func() error) {
|
||||
}
|
||||
|
||||
func (jm *jobManager) worker() {
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
buf := make([]byte, stackTraceBufferSize)
|
||||
buf = buf[:runtime.Stack(buf, false)]
|
||||
log.Printf("panic: certificate worker: %v\n%s", err, buf)
|
||||
}
|
||||
}()
|
||||
|
||||
for {
|
||||
jm.mu.Lock()
|
||||
if len(jm.queue) == 0 {
|
||||
@ -59,7 +71,9 @@ func (jm *jobManager) worker() {
|
||||
jm.queue = jm.queue[1:]
|
||||
jm.mu.Unlock()
|
||||
if err := next.job(); err != nil {
|
||||
log.Printf("[ERROR] %v", err)
|
||||
if next.logger != nil {
|
||||
next.logger.Error("job failed", zap.Error(err))
|
||||
}
|
||||
}
|
||||
if next.name != "" {
|
||||
jm.mu.Lock()
|
||||
@ -69,7 +83,7 @@ func (jm *jobManager) worker() {
|
||||
}
|
||||
}
|
||||
|
||||
func doWithRetry(ctx context.Context, f func(context.Context) error) error {
|
||||
func doWithRetry(ctx context.Context, log *zap.Logger, f func(context.Context) error) error {
|
||||
var attempts int
|
||||
ctx = context.WithValue(ctx, AttemptsCtxKey, &attempts)
|
||||
|
||||
@ -102,11 +116,22 @@ func doWithRetry(ctx context.Context, f func(context.Context) error) error {
|
||||
intervalIndex++
|
||||
}
|
||||
if time.Since(start) < maxRetryDuration {
|
||||
log.Printf("[ERROR] attempt %d: %v - retrying in %s (%s/%s elapsed)...",
|
||||
attempts, err, retryIntervals[intervalIndex], time.Since(start), maxRetryDuration)
|
||||
if log != nil {
|
||||
log.Error("will retry",
|
||||
zap.Error(err),
|
||||
zap.Int("attempt", attempts),
|
||||
zap.Duration("retrying_in", retryIntervals[intervalIndex]),
|
||||
zap.Duration("elapsed", time.Since(start)),
|
||||
zap.Duration("max_duration", maxRetryDuration))
|
||||
}
|
||||
} else {
|
||||
log.Printf("[ERROR] final attempt: %v - giving up (%s/%s elapsed)...",
|
||||
err, time.Since(start), maxRetryDuration)
|
||||
if log != nil {
|
||||
log.Error("final attempt; giving up",
|
||||
zap.Error(err),
|
||||
zap.Int("attempt", attempts),
|
||||
zap.Duration("elapsed", time.Since(start)),
|
||||
zap.Duration("max_duration", maxRetryDuration))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
@ -16,10 +16,12 @@ package certmagic
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
weakrand "math/rand" // seeded elsewhere
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
// Cache is a structure that stores certificates in memory.
|
||||
@ -62,6 +64,8 @@ type Cache struct {
|
||||
|
||||
// Used to signal when stopping is completed
|
||||
doneChan chan struct{}
|
||||
|
||||
logger *zap.Logger
|
||||
}
|
||||
|
||||
// NewCache returns a new, valid Cache for efficiently
|
||||
@ -94,6 +98,9 @@ func NewCache(opts CacheOptions) *Cache {
|
||||
if opts.RenewCheckInterval <= 0 {
|
||||
opts.RenewCheckInterval = DefaultRenewCheckInterval
|
||||
}
|
||||
if opts.Capacity < 0 {
|
||||
opts.Capacity = 0
|
||||
}
|
||||
|
||||
// this must be set, because we cannot not
|
||||
// safely assume that the Default Config
|
||||
@ -108,9 +115,10 @@ func NewCache(opts CacheOptions) *Cache {
|
||||
cacheIndex: make(map[string][]string),
|
||||
stopChan: make(chan struct{}),
|
||||
doneChan: make(chan struct{}),
|
||||
logger: opts.Logger,
|
||||
}
|
||||
|
||||
go c.maintainAssets()
|
||||
go c.maintainAssets(0)
|
||||
|
||||
return c
|
||||
}
|
||||
@ -153,6 +161,14 @@ type CacheOptions struct {
|
||||
// How often to check certificates for renewal;
|
||||
// if unset, DefaultRenewCheckInterval will be used.
|
||||
RenewCheckInterval time.Duration
|
||||
|
||||
// Maximum number of certificates to allow in the cache.
|
||||
// If reached, certificates will be randomly evicted to
|
||||
// make room for new ones. 0 means unlimited.
|
||||
Capacity int
|
||||
|
||||
// Set a logger to enable logging
|
||||
Logger *zap.Logger
|
||||
}
|
||||
|
||||
// ConfigGetter is a function that returns a prepared,
|
||||
@ -181,6 +197,25 @@ func (certCache *Cache) unsyncedCacheCertificate(cert Certificate) {
|
||||
return
|
||||
}
|
||||
|
||||
// if the cache is at capacity, make room for new cert
|
||||
cacheSize := len(certCache.cache)
|
||||
if certCache.options.Capacity > 0 && cacheSize >= certCache.options.Capacity {
|
||||
// Go maps are "nondeterministic" but not actually random,
|
||||
// so although we could just chop off the "front" of the
|
||||
// map with less code, that is a heavily skewed eviction
|
||||
// strategy; generating random numbers is cheap and
|
||||
// ensures a much better distribution.
|
||||
rnd := weakrand.Intn(cacheSize)
|
||||
i := 0
|
||||
for _, randomCert := range certCache.cache {
|
||||
if i == rnd {
|
||||
certCache.removeCertificate(randomCert)
|
||||
break
|
||||
}
|
||||
i++
|
||||
}
|
||||
}
|
||||
|
||||
// store the certificate
|
||||
certCache.cache[cert.hash] = cert
|
||||
|
||||
@ -223,16 +258,11 @@ func (certCache *Cache) replaceCertificate(oldCert, newCert Certificate) {
|
||||
certCache.removeCertificate(oldCert)
|
||||
certCache.unsyncedCacheCertificate(newCert)
|
||||
certCache.mu.Unlock()
|
||||
log.Printf("[INFO] Replaced certificate in cache for %v (new expiration date: %s)",
|
||||
newCert.Names, newCert.Leaf.NotAfter.Format("2006-01-02 15:04:05"))
|
||||
}
|
||||
|
||||
func (certCache *Cache) getFirstMatchingCert(name string) (Certificate, bool) {
|
||||
all := certCache.getAllMatchingCerts(name)
|
||||
if len(all) == 0 {
|
||||
return all[0], true
|
||||
if certCache.logger != nil {
|
||||
certCache.logger.Info("replaced certificate in cache",
|
||||
zap.Strings("identifiers", newCert.Names),
|
||||
zap.Time("new_expiration", newCert.Leaf.NotAfter))
|
||||
}
|
||||
return Certificate{}, false
|
||||
}
|
||||
|
||||
func (certCache *Cache) getAllMatchingCerts(name string) []Certificate {
|
@ -19,11 +19,11 @@ import (
|
||||
"crypto/x509"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"go.uber.org/zap"
|
||||
"golang.org/x/crypto/ocsp"
|
||||
)
|
||||
|
||||
@ -57,6 +57,18 @@ func (cert Certificate) NeedsRenewal(cfg *Config) bool {
|
||||
return currentlyInRenewalWindow(cert.Leaf.NotBefore, cert.Leaf.NotAfter, cfg.RenewalWindowRatio)
|
||||
}
|
||||
|
||||
// Expired returns true if the certificate has expired.
|
||||
func (cert Certificate) Expired() bool {
|
||||
if cert.Leaf == nil {
|
||||
// ideally cert.Leaf would never be nil, but this can happen for
|
||||
// "synthetic" certs like those made to solve the TLS-ALPN challenge
|
||||
// which adds a special cert directly to the cache, since
|
||||
// tls.X509KeyPair() discards the leaf; oh well
|
||||
return false
|
||||
}
|
||||
return time.Now().After(cert.Leaf.NotAfter)
|
||||
}
|
||||
|
||||
// currentlyInRenewalWindow returns true if the current time is
|
||||
// within the renewal window, according to the given start/end
|
||||
// dates and the ratio of the renewal window. If true is returned,
|
||||
@ -108,7 +120,7 @@ func (cfg *Config) loadManagedCertificate(domain string) (Certificate, error) {
|
||||
if err != nil {
|
||||
return Certificate{}, err
|
||||
}
|
||||
cert, err := makeCertificateWithOCSP(cfg.Storage, certRes.CertificatePEM, certRes.PrivateKeyPEM)
|
||||
cert, err := cfg.makeCertificateWithOCSP(certRes.CertificatePEM, certRes.PrivateKeyPEM)
|
||||
if err != nil {
|
||||
return cert, err
|
||||
}
|
||||
@ -122,7 +134,7 @@ func (cfg *Config) loadManagedCertificate(domain string) (Certificate, error) {
|
||||
//
|
||||
// This method is safe for concurrent use.
|
||||
func (cfg *Config) CacheUnmanagedCertificatePEMFile(certFile, keyFile string, tags []string) error {
|
||||
cert, err := makeCertificateFromDiskWithOCSP(cfg.Storage, certFile, keyFile)
|
||||
cert, err := cfg.makeCertificateFromDiskWithOCSP(cfg.Storage, certFile, keyFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -143,8 +155,8 @@ func (cfg *Config) CacheUnmanagedTLSCertificate(tlsCert tls.Certificate, tags []
|
||||
return err
|
||||
}
|
||||
_, err = stapleOCSP(cfg.Storage, &cert, nil)
|
||||
if err != nil {
|
||||
log.Printf("[WARNING] Stapling OCSP: %v", err)
|
||||
if err != nil && cfg.Logger != nil {
|
||||
cfg.Logger.Warn("stapling OCSP", zap.Error(err))
|
||||
}
|
||||
cfg.emit("cached_unmanaged_cert", cert.Names)
|
||||
cert.Tags = tags
|
||||
@ -157,7 +169,7 @@ func (cfg *Config) CacheUnmanagedTLSCertificate(tlsCert tls.Certificate, tags []
|
||||
//
|
||||
// This method is safe for concurrent use.
|
||||
func (cfg *Config) CacheUnmanagedCertificatePEMBytes(certBytes, keyBytes []byte, tags []string) error {
|
||||
cert, err := makeCertificateWithOCSP(cfg.Storage, certBytes, keyBytes)
|
||||
cert, err := cfg.makeCertificateWithOCSP(certBytes, keyBytes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -171,7 +183,7 @@ func (cfg *Config) CacheUnmanagedCertificatePEMBytes(certBytes, keyBytes []byte,
|
||||
// certificate and key files. It fills out all the fields in
|
||||
// the certificate except for the Managed and OnDemand flags.
|
||||
// (It is up to the caller to set those.) It staples OCSP.
|
||||
func makeCertificateFromDiskWithOCSP(storage Storage, certFile, keyFile string) (Certificate, error) {
|
||||
func (cfg Config) makeCertificateFromDiskWithOCSP(storage Storage, certFile, keyFile string) (Certificate, error) {
|
||||
certPEMBlock, err := ioutil.ReadFile(certFile)
|
||||
if err != nil {
|
||||
return Certificate{}, err
|
||||
@ -180,19 +192,19 @@ func makeCertificateFromDiskWithOCSP(storage Storage, certFile, keyFile string)
|
||||
if err != nil {
|
||||
return Certificate{}, err
|
||||
}
|
||||
return makeCertificateWithOCSP(storage, certPEMBlock, keyPEMBlock)
|
||||
return cfg.makeCertificateWithOCSP(certPEMBlock, keyPEMBlock)
|
||||
}
|
||||
|
||||
// makeCertificateWithOCSP is the same as makeCertificate except that it also
|
||||
// staples OCSP to the certificate.
|
||||
func makeCertificateWithOCSP(storage Storage, certPEMBlock, keyPEMBlock []byte) (Certificate, error) {
|
||||
func (cfg Config) makeCertificateWithOCSP(certPEMBlock, keyPEMBlock []byte) (Certificate, error) {
|
||||
cert, err := makeCertificate(certPEMBlock, keyPEMBlock)
|
||||
if err != nil {
|
||||
return cert, err
|
||||
}
|
||||
_, err = stapleOCSP(storage, &cert, certPEMBlock)
|
||||
if err != nil {
|
||||
log.Printf("[WARNING] Stapling OCSP: %v", err)
|
||||
_, err = stapleOCSP(cfg.Storage, &cert, certPEMBlock)
|
||||
if err != nil && cfg.Logger != nil {
|
||||
cfg.Logger.Warn("stapling OCSP", zap.Error(err))
|
||||
}
|
||||
return cert, nil
|
||||
}
|
||||
@ -304,7 +316,9 @@ func (cfg *Config) managedCertInStorageExpiresSoon(cert Certificate) (bool, erro
|
||||
// to the new cert. It assumes that the new certificate for oldCert.Names[0] is
|
||||
// already in storage.
|
||||
func (cfg *Config) reloadManagedCertificate(oldCert Certificate) error {
|
||||
log.Printf("[INFO] Reloading managed certificate for %v", oldCert.Names)
|
||||
if cfg.Logger != nil {
|
||||
cfg.Logger.Info("reloading managed certificate", zap.Strings("identifiers", oldCert.Names))
|
||||
}
|
||||
newCert, err := cfg.loadManagedCertificate(oldCert.Names[0])
|
||||
if err != nil {
|
||||
return fmt.Errorf("loading managed certificate for %v from storage: %v", oldCert.Names, err)
|
||||
@ -342,32 +356,39 @@ func SubjectQualifiesForCert(subj string) bool {
|
||||
// allowed, as long as they conform to CABF requirements (only
|
||||
// one wildcard label, and it must be the left-most label).
|
||||
func SubjectQualifiesForPublicCert(subj string) bool {
|
||||
// must at least qualify for certificate
|
||||
// must at least qualify for a certificate
|
||||
return SubjectQualifiesForCert(subj) &&
|
||||
|
||||
// localhost is ineligible
|
||||
subj != "localhost" &&
|
||||
// localhost, .localhost TLD, and .local TLD are ineligible
|
||||
!SubjectIsInternal(subj) &&
|
||||
|
||||
// .localhost TLD is ineligible
|
||||
!strings.HasSuffix(subj, ".localhost") &&
|
||||
|
||||
// .local TLD is ineligible
|
||||
!strings.HasSuffix(subj, ".local") &&
|
||||
// cannot be an IP address (as of yet), see
|
||||
// https://community.letsencrypt.org/t/certificate-for-static-ip/84/2?u=mholt
|
||||
!SubjectIsIP(subj) &&
|
||||
|
||||
// only one wildcard label allowed, and it must be left-most
|
||||
(!strings.Contains(subj, "*") ||
|
||||
(strings.Count(subj, "*") == 1 &&
|
||||
len(subj) > 2 &&
|
||||
strings.HasPrefix(subj, "*."))) &&
|
||||
strings.HasPrefix(subj, "*.")))
|
||||
}
|
||||
|
||||
// cannot be an IP address (as of yet), see
|
||||
// https://community.letsencrypt.org/t/certificate-for-static-ip/84/2?u=mholt
|
||||
net.ParseIP(subj) == nil
|
||||
// SubjectIsIP returns true if subj is an IP address.
|
||||
func SubjectIsIP(subj string) bool {
|
||||
return net.ParseIP(subj) != nil
|
||||
}
|
||||
|
||||
// SubjectIsInternal returns true if subj is an internal-facing
|
||||
// hostname or address.
|
||||
func SubjectIsInternal(subj string) bool {
|
||||
return subj == "localhost" ||
|
||||
strings.HasSuffix(subj, ".localhost") ||
|
||||
strings.HasSuffix(subj, ".local")
|
||||
}
|
||||
|
||||
// MatchWildcard returns true if subject (a candidate DNS name)
|
||||
// matches wildcard (a reference DNS name), mostly according to
|
||||
// RFC-compliant wildcard rules.
|
||||
// RFC6125-compliant wildcard rules.
|
||||
func MatchWildcard(subject, wildcard string) bool {
|
||||
if subject == wildcard {
|
||||
return true
|
@ -91,7 +91,10 @@ func HTTPS(domainNames []string, mux http.Handler) error {
|
||||
return err
|
||||
}
|
||||
|
||||
httpsLn, err = tls.Listen("tcp", fmt.Sprintf(":%d", HTTPSPort), cfg.TLSConfig())
|
||||
tlsConfig := cfg.TLSConfig()
|
||||
tlsConfig.NextProtos = append([]string{"h2", "http/1.1"}, tlsConfig.NextProtos...)
|
||||
|
||||
httpsLn, err = tls.Listen("tcp", fmt.Sprintf(":%d", HTTPSPort), tlsConfig)
|
||||
if err != nil {
|
||||
httpLn.Close()
|
||||
httpLn = nil
|
||||
@ -336,7 +339,7 @@ func hostOnly(hostport string) string {
|
||||
// identical calls) to Issue(), giving the issuer the option to ensure
|
||||
// it has all the necessary information/state.
|
||||
type PreChecker interface {
|
||||
PreCheck(names []string, interactive bool) error
|
||||
PreCheck(ctx context.Context, names []string, interactive bool) error
|
||||
}
|
||||
|
||||
// Issuer is a type that can issue certificates.
|
||||
@ -362,9 +365,11 @@ type Issuer interface {
|
||||
IssuerKey() string
|
||||
}
|
||||
|
||||
// Revoker can revoke certificates.
|
||||
// Revoker can revoke certificates. Reason codes are defined
|
||||
// by RFC 5280 §5.3.1: https://tools.ietf.org/html/rfc5280#section-5.3.1
|
||||
// and are available as constants in our ACME library.
|
||||
type Revoker interface {
|
||||
Revoke(ctx context.Context, cert CertificateResource) error
|
||||
Revoke(ctx context.Context, cert CertificateResource, reason int) error
|
||||
}
|
||||
|
||||
// KeyGenerator can generate a private key.
|
||||
@ -470,3 +475,6 @@ var (
|
||||
lnMu sync.Mutex
|
||||
httpWg sync.WaitGroup
|
||||
)
|
||||
|
||||
// Maximum size for the stack trace when recovering from panics.
|
||||
const stackTraceBufferSize = 1024 * 128
|
@ -24,14 +24,14 @@ import (
|
||||
"crypto/x509/pkix"
|
||||
"encoding/asn1"
|
||||
"fmt"
|
||||
"log"
|
||||
weakrand "math/rand"
|
||||
"net"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/go-acme/lego/v3/challenge/tlsalpn01"
|
||||
"github.com/mholt/acmez"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
// Config configures a certificate manager instance.
|
||||
@ -42,7 +42,7 @@ type Config struct {
|
||||
// renewal window, which is the span of time at the
|
||||
// end of the certificate's validity period in which
|
||||
// it should be renewed; for most certificates, the
|
||||
// global default is good, but for exremely short-
|
||||
// global default is good, but for extremely short-
|
||||
// lived certs, you may want to raise this to ~0.5.
|
||||
RenewalWindowRatio float64
|
||||
|
||||
@ -64,8 +64,7 @@ type Config struct {
|
||||
// TODO: Can we call this feature "Reactive/Lazy/Passive TLS" instead?
|
||||
OnDemand *OnDemandConfig
|
||||
|
||||
// Add the must staple TLS extension to the
|
||||
// CSR generated by lego/acme
|
||||
// Add the must staple TLS extension to the CSR
|
||||
MustStaple bool
|
||||
|
||||
// The type that issues certificates; the
|
||||
@ -93,6 +92,9 @@ type Config struct {
|
||||
// loading TLS assets
|
||||
Storage Storage
|
||||
|
||||
// Set a logger to enable logging
|
||||
Logger *zap.Logger
|
||||
|
||||
// required pointer to the in-memory cert cache
|
||||
certCache *Cache
|
||||
}
|
||||
@ -328,7 +330,7 @@ func (cfg *Config) manageOne(ctx context.Context, domainName string, async bool)
|
||||
// either the old one (or sometimes the new one) is about to be
|
||||
// canceled. This seems like reasonable logic for any consumer
|
||||
// of this lib. See https://github.com/caddyserver/caddy/issues/3202
|
||||
jm.Submit("", obtain)
|
||||
jm.Submit(cfg.Logger, "", obtain)
|
||||
return nil
|
||||
}
|
||||
return obtain()
|
||||
@ -349,7 +351,7 @@ func (cfg *Config) manageOne(ctx context.Context, domainName string, async bool)
|
||||
}
|
||||
if cert.NeedsRenewal(cfg) {
|
||||
if async {
|
||||
jm.Submit("renew_"+domainName, renew)
|
||||
jm.Submit(cfg.Logger, "renew_"+domainName, renew)
|
||||
return nil
|
||||
}
|
||||
return renew()
|
||||
@ -373,7 +375,7 @@ func (cfg *Config) ObtainCert(ctx context.Context, name string, interactive bool
|
||||
if cfg.storageHasCertResources(name) {
|
||||
return nil
|
||||
}
|
||||
issuer, err := cfg.getPrecheckedIssuer([]string{name}, interactive)
|
||||
issuer, err := cfg.getPrecheckedIssuer(ctx, []string{name}, interactive)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -383,27 +385,49 @@ func (cfg *Config) ObtainCert(ctx context.Context, name string, interactive bool
|
||||
return cfg.obtainWithIssuer(ctx, issuer, name, interactive)
|
||||
}
|
||||
|
||||
func loggerNamed(l *zap.Logger, name string) *zap.Logger {
|
||||
if l == nil {
|
||||
return nil
|
||||
}
|
||||
return l.Named(name)
|
||||
}
|
||||
|
||||
func (cfg *Config) obtainWithIssuer(ctx context.Context, issuer Issuer, name string, interactive bool) error {
|
||||
log.Printf("[INFO][%s] Obtain certificate; acquiring lock...", name)
|
||||
log := loggerNamed(cfg.Logger, "obtain")
|
||||
|
||||
if log != nil {
|
||||
log.Info("acquiring lock", zap.String("identifier", name))
|
||||
}
|
||||
|
||||
// ensure idempotency of the obtain operation for this name
|
||||
lockKey := cfg.lockKey("cert_acme", name)
|
||||
err := obtainLock(cfg.Storage, lockKey)
|
||||
err := acquireLock(ctx, cfg.Storage, lockKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
log.Printf("[INFO][%s] Obtain: Releasing lock", name)
|
||||
if log != nil {
|
||||
log.Info("releasing lock", zap.String("identifier", name))
|
||||
}
|
||||
if err := releaseLock(cfg.Storage, lockKey); err != nil {
|
||||
log.Printf("[ERROR][%s] Obtain: Unable to unlock '%s': %v", name, lockKey, err)
|
||||
if log != nil {
|
||||
log.Error("unable to unlock",
|
||||
zap.String("identifier", name),
|
||||
zap.String("lock_key", lockKey),
|
||||
zap.Error(err))
|
||||
}
|
||||
}
|
||||
}()
|
||||
log.Printf("[INFO][%s] Obtain: Lock acquired; proceeding...", name)
|
||||
if log != nil {
|
||||
log.Info("lock acquired", zap.String("identifier", name))
|
||||
}
|
||||
|
||||
f := func(ctx context.Context) error {
|
||||
// check if obtain is still needed -- might have been obtained during lock
|
||||
if cfg.storageHasCertResources(name) {
|
||||
log.Printf("[INFO][%s] Obtain: Certificate already exists in storage", name)
|
||||
if log != nil {
|
||||
log.Info("certificate already exists in storage", zap.String("identifier", name))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -437,29 +461,30 @@ func (cfg *Config) obtainWithIssuer(ctx context.Context, issuer Issuer, name str
|
||||
if err != nil {
|
||||
return fmt.Errorf("[%s] Obtain: saving assets: %v", name, err)
|
||||
}
|
||||
|
||||
cfg.emit("cert_obtained", name)
|
||||
|
||||
if log != nil {
|
||||
log.Info("certificate obtained successfully", zap.String("identifier", name))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
if interactive {
|
||||
err = f(ctx)
|
||||
} else {
|
||||
err = doWithRetry(ctx, f)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
err = doWithRetry(ctx, log, f)
|
||||
}
|
||||
|
||||
cfg.emit("cert_obtained", name)
|
||||
|
||||
log.Printf("[INFO][%s] Certificate obtained successfully", name)
|
||||
|
||||
return nil
|
||||
return err
|
||||
}
|
||||
|
||||
// RenewCert renews the certificate for name using cfg. It stows the
|
||||
// renewed certificate and its assets in storage if successful. It
|
||||
// DOES NOT update the in-memory cache with the new certificate.
|
||||
func (cfg *Config) RenewCert(ctx context.Context, name string, interactive bool) error {
|
||||
issuer, err := cfg.getPrecheckedIssuer([]string{name}, interactive)
|
||||
issuer, err := cfg.getPrecheckedIssuer(ctx, []string{name}, interactive)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -470,21 +495,34 @@ func (cfg *Config) RenewCert(ctx context.Context, name string, interactive bool)
|
||||
}
|
||||
|
||||
func (cfg *Config) renewWithIssuer(ctx context.Context, issuer Issuer, name string, interactive bool) error {
|
||||
log.Printf("[INFO][%s] Renew certificate; acquiring lock...", name)
|
||||
log := loggerNamed(cfg.Logger, "renew")
|
||||
|
||||
if log != nil {
|
||||
log.Info("acquiring lock", zap.String("identifier", name))
|
||||
}
|
||||
|
||||
// ensure idempotency of the renew operation for this name
|
||||
lockKey := cfg.lockKey("cert_acme", name)
|
||||
err := obtainLock(cfg.Storage, lockKey)
|
||||
err := acquireLock(ctx, cfg.Storage, lockKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
log.Printf("[INFO][%s] Renew: Releasing lock", name)
|
||||
if log != nil {
|
||||
log.Info("releasing lock", zap.String("identifier", name))
|
||||
}
|
||||
if err := releaseLock(cfg.Storage, lockKey); err != nil {
|
||||
log.Printf("[ERROR][%s] Renew: Unable to unlock '%s': %v", name, lockKey, err)
|
||||
if log != nil {
|
||||
log.Error("unable to unlock",
|
||||
zap.String("identifier", name),
|
||||
zap.String("lock_key", lockKey),
|
||||
zap.Error(err))
|
||||
}
|
||||
}
|
||||
}()
|
||||
log.Printf("[INFO][%s] Renew: Lock acquired; proceeding...", name)
|
||||
if log != nil {
|
||||
log.Info("lock acquired", zap.String("identifier", name))
|
||||
}
|
||||
|
||||
f := func(ctx context.Context) error {
|
||||
// prepare for renewal (load PEM cert, key, and meta)
|
||||
@ -496,10 +534,18 @@ func (cfg *Config) renewWithIssuer(ctx context.Context, issuer Issuer, name stri
|
||||
// check if renew is still needed - might have been renewed while waiting for lock
|
||||
timeLeft, needsRenew := cfg.managedCertNeedsRenewal(certRes)
|
||||
if !needsRenew {
|
||||
log.Printf("[INFO][%s] Renew: Certificate appears to have been renewed already (expires in %s)", name, timeLeft)
|
||||
if log != nil {
|
||||
log.Info("certificate appears to have been renewed already",
|
||||
zap.String("identifier", name),
|
||||
zap.Duration("remaining", timeLeft))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
log.Printf("[INFO][%s] Renew: %s remaining", name, timeLeft)
|
||||
if log != nil {
|
||||
log.Info("renewing certificate",
|
||||
zap.String("identifier", name),
|
||||
zap.Duration("remaining", timeLeft))
|
||||
}
|
||||
|
||||
privateKey, err := decodePrivateKey(certRes.PrivateKeyPEM)
|
||||
if err != nil {
|
||||
@ -526,22 +572,23 @@ func (cfg *Config) renewWithIssuer(ctx context.Context, issuer Issuer, name stri
|
||||
if err != nil {
|
||||
return fmt.Errorf("[%s] Renew: saving assets: %v", name, err)
|
||||
}
|
||||
|
||||
cfg.emit("cert_renewed", name)
|
||||
|
||||
if log != nil {
|
||||
log.Info("certificate renewed successfully", zap.String("identifier", name))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
if interactive {
|
||||
err = f(ctx)
|
||||
} else {
|
||||
err = doWithRetry(ctx, f)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
err = doWithRetry(ctx, log, f)
|
||||
}
|
||||
|
||||
cfg.emit("cert_renewed", name)
|
||||
|
||||
log.Printf("[INFO][%s] Certificate renewed successfully", name)
|
||||
|
||||
return nil
|
||||
return err
|
||||
}
|
||||
|
||||
func (cfg *Config) generateCSR(privateKey crypto.PrivateKey, sans []string) (*x509.CertificateRequest, error) {
|
||||
@ -573,8 +620,8 @@ func (cfg *Config) generateCSR(privateKey crypto.PrivateKey, sans []string) (*x5
|
||||
|
||||
// RevokeCert revokes the certificate for domain via ACME protocol. It requires
|
||||
// that cfg.Issuer is properly configured with the same issuer that issued the
|
||||
// certificate being revoked.
|
||||
func (cfg *Config) RevokeCert(ctx context.Context, domain string, interactive bool) error {
|
||||
// certificate being revoked. See RFC 5280 §5.3.1 for reason codes.
|
||||
func (cfg *Config) RevokeCert(ctx context.Context, domain string, reason int, interactive bool) error {
|
||||
rev := cfg.Revoker
|
||||
if rev == nil {
|
||||
rev = Default.Revoker
|
||||
@ -591,7 +638,7 @@ func (cfg *Config) RevokeCert(ctx context.Context, domain string, interactive bo
|
||||
return fmt.Errorf("private key not found for %s", certRes.SANs)
|
||||
}
|
||||
|
||||
err = rev.Revoke(ctx, certRes)
|
||||
err = rev.Revoke(ctx, certRes, reason)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -632,7 +679,7 @@ func (cfg *Config) TLSConfig() *tls.Config {
|
||||
return &tls.Config{
|
||||
// these two fields necessary for TLS-ALPN challenge
|
||||
GetCertificate: cfg.GetCertificate,
|
||||
NextProtos: []string{"h2", "http/1.1", tlsalpn01.ACMETLS1Protocol},
|
||||
NextProtos: []string{acmez.ACMETLS1Protocol},
|
||||
|
||||
// the rest recommended for modern TLS servers
|
||||
MinVersion: tls.VersionTLS12,
|
||||
@ -650,7 +697,7 @@ func (cfg *Config) TLSConfig() *tls.Config {
|
||||
// that storage is functioning. If a nil Issuer is returned
|
||||
// with a nil error, that means to skip this operation
|
||||
// (not an error, just a no-op).
|
||||
func (cfg *Config) getPrecheckedIssuer(names []string, interactive bool) (Issuer, error) {
|
||||
func (cfg *Config) getPrecheckedIssuer(ctx context.Context, names []string, interactive bool) (Issuer, error) {
|
||||
// ensure storage is writeable and readable
|
||||
// TODO: this is not necessary every time; should only
|
||||
// perform check once every so often for each storage,
|
||||
@ -660,7 +707,7 @@ func (cfg *Config) getPrecheckedIssuer(names []string, interactive bool) (Issuer
|
||||
return nil, fmt.Errorf("failed storage check: %v - storage is probably misconfigured", err)
|
||||
}
|
||||
if prechecker, ok := cfg.Issuer.(PreChecker); ok {
|
||||
err := prechecker.PreCheck(names, interactive)
|
||||
err := prechecker.PreCheck(ctx, names, interactive)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -686,7 +733,10 @@ func (cfg *Config) checkStorage() error {
|
||||
defer func() {
|
||||
deleteErr := cfg.Storage.Delete(key)
|
||||
if deleteErr != nil {
|
||||
log.Printf("[ERROR] Deleting test key %s from storage: %v", key, err)
|
||||
if cfg.Logger != nil {
|
||||
cfg.Logger.Error("deleting test key from storage",
|
||||
zap.String("key", key), zap.Error(err))
|
||||
}
|
||||
}
|
||||
// if there was no other error, make sure
|
||||
// to return any error returned from Delete
|
@ -48,7 +48,7 @@ func encodePrivateKey(key crypto.PrivateKey) ([]byte, error) {
|
||||
case *rsa.PrivateKey:
|
||||
pemType = "RSA"
|
||||
keyBytes = x509.MarshalPKCS1PrivateKey(key)
|
||||
case *ed25519.PrivateKey:
|
||||
case ed25519.PrivateKey:
|
||||
var err error
|
||||
pemType = "ED25519"
|
||||
keyBytes, err = x509.MarshalPKCS8PrivateKey(key)
|
||||
@ -66,7 +66,7 @@ func encodePrivateKey(key crypto.PrivateKey) ([]byte, error) {
|
||||
// Borrowed from Go standard library, to handle various private key and PEM block types.
|
||||
// https://github.com/golang/go/blob/693748e9fa385f1e2c3b91ca9acbb6c0ad2d133d/src/crypto/tls/tls.go#L291-L308
|
||||
// https://github.com/golang/go/blob/693748e9fa385f1e2c3b91ca9acbb6c0ad2d133d/src/crypto/tls/tls.go#L238)
|
||||
func decodePrivateKey(keyPEMBytes []byte) (crypto.PrivateKey, error) {
|
||||
func decodePrivateKey(keyPEMBytes []byte) (crypto.Signer, error) {
|
||||
keyBlockDER, _ := pem.Decode(keyPEMBytes)
|
||||
|
||||
if keyBlockDER.Type != "PRIVATE KEY" && !strings.HasSuffix(keyBlockDER.Type, " PRIVATE KEY") {
|
||||
@ -80,7 +80,7 @@ func decodePrivateKey(keyPEMBytes []byte) (crypto.PrivateKey, error) {
|
||||
if key, err := x509.ParsePKCS8PrivateKey(keyBlockDER.Bytes); err == nil {
|
||||
switch key := key.(type) {
|
||||
case *rsa.PrivateKey, *ecdsa.PrivateKey, ed25519.PrivateKey:
|
||||
return key, nil
|
||||
return key.(crypto.Signer), nil
|
||||
default:
|
||||
return nil, fmt.Errorf("found unknown private key type in PKCS#8 wrapping: %T", key)
|
||||
}
|
||||
@ -177,6 +177,51 @@ func (cfg *Config) loadCertResource(certNamesKey string) (CertificateResource, e
|
||||
if err != nil {
|
||||
return CertificateResource{}, fmt.Errorf("decoding certificate metadata: %v", err)
|
||||
}
|
||||
|
||||
// TODO: July 2020 - transition to new ACME lib and cert resource structure;
|
||||
// for a while, we will need to convert old cert resources to new structure
|
||||
certRes, err = cfg.transitionCertMetaToACMEzJuly2020Format(certRes, metaBytes)
|
||||
if err != nil {
|
||||
return certRes, fmt.Errorf("one-time certificate resource transition: %v", err)
|
||||
}
|
||||
|
||||
return certRes, nil
|
||||
}
|
||||
|
||||
// TODO: this is a temporary transition helper starting July 2020.
|
||||
// It can go away when we think enough time has passed that most active assets have transitioned.
|
||||
func (cfg *Config) transitionCertMetaToACMEzJuly2020Format(certRes CertificateResource, metaBytes []byte) (CertificateResource, error) {
|
||||
data, ok := certRes.IssuerData.(map[string]interface{})
|
||||
if !ok {
|
||||
return certRes, nil
|
||||
}
|
||||
if certURL, ok := data["url"].(string); ok && certURL != "" {
|
||||
return certRes, nil
|
||||
}
|
||||
|
||||
var oldCertRes struct {
|
||||
SANs []string `json:"sans"`
|
||||
IssuerData struct {
|
||||
Domain string `json:"domain"`
|
||||
CertURL string `json:"certUrl"`
|
||||
CertStableURL string `json:"certStableUrl"`
|
||||
} `json:"issuer_data"`
|
||||
}
|
||||
err := json.Unmarshal(metaBytes, &oldCertRes)
|
||||
if err != nil {
|
||||
return certRes, fmt.Errorf("decoding into old certificate resource type: %v", err)
|
||||
}
|
||||
|
||||
data = map[string]interface{}{
|
||||
"url": oldCertRes.IssuerData.CertURL,
|
||||
}
|
||||
certRes.IssuerData = data
|
||||
|
||||
err = cfg.saveCertResource(certRes)
|
||||
if err != nil {
|
||||
return certRes, fmt.Errorf("saving converted certificate resource: %v", err)
|
||||
}
|
||||
|
||||
return certRes, nil
|
||||
}
|
||||
|
339
vendor/github.com/coolaj86/certmagic/dnsutil.go
generated
vendored
Normal file
339
vendor/github.com/coolaj86/certmagic/dnsutil.go
generated
vendored
Normal file
@ -0,0 +1,339 @@
|
||||
package certmagic
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/miekg/dns"
|
||||
)
|
||||
|
||||
// Code in this file adapted from go-acme/lego, July 2020:
|
||||
// https://github.com/go-acme/lego
|
||||
// by Ludovic Fernandez and Dominik Menke
|
||||
//
|
||||
// It has been modified.
|
||||
|
||||
// findZoneByFQDN determines the zone apex for the given fqdn by recursing
|
||||
// up the domain labels until the nameserver returns a SOA record in the
|
||||
// answer section.
|
||||
func findZoneByFQDN(fqdn string, nameservers []string) (string, error) {
|
||||
if !strings.HasSuffix(fqdn, ".") {
|
||||
fqdn += "."
|
||||
}
|
||||
soa, err := lookupSoaByFqdn(fqdn, nameservers)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return soa.zone, nil
|
||||
}
|
||||
|
||||
func lookupSoaByFqdn(fqdn string, nameservers []string) (*soaCacheEntry, error) {
|
||||
if !strings.HasSuffix(fqdn, ".") {
|
||||
fqdn += "."
|
||||
}
|
||||
|
||||
fqdnSOACacheMu.Lock()
|
||||
defer fqdnSOACacheMu.Unlock()
|
||||
|
||||
// prefer cached version if fresh
|
||||
if ent := fqdnSOACache[fqdn]; ent != nil && !ent.isExpired() {
|
||||
return ent, nil
|
||||
}
|
||||
|
||||
ent, err := fetchSoaByFqdn(fqdn, nameservers)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// save result to cache, but don't allow
|
||||
// the cache to grow out of control
|
||||
if len(fqdnSOACache) >= 1000 {
|
||||
for key := range fqdnSOACache {
|
||||
delete(fqdnSOACache, key)
|
||||
break
|
||||
}
|
||||
}
|
||||
fqdnSOACache[fqdn] = ent
|
||||
|
||||
return ent, nil
|
||||
}
|
||||
|
||||
func fetchSoaByFqdn(fqdn string, nameservers []string) (*soaCacheEntry, error) {
|
||||
var err error
|
||||
var in *dns.Msg
|
||||
|
||||
labelIndexes := dns.Split(fqdn)
|
||||
for _, index := range labelIndexes {
|
||||
domain := fqdn[index:]
|
||||
|
||||
in, err = dnsQuery(domain, dns.TypeSOA, nameservers, true)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if in == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
switch in.Rcode {
|
||||
case dns.RcodeSuccess:
|
||||
// Check if we got a SOA RR in the answer section
|
||||
if len(in.Answer) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
// CNAME records cannot/should not exist at the root of a zone.
|
||||
// So we skip a domain when a CNAME is found.
|
||||
if dnsMsgContainsCNAME(in) {
|
||||
continue
|
||||
}
|
||||
|
||||
for _, ans := range in.Answer {
|
||||
if soa, ok := ans.(*dns.SOA); ok {
|
||||
return newSoaCacheEntry(soa), nil
|
||||
}
|
||||
}
|
||||
case dns.RcodeNameError:
|
||||
// NXDOMAIN
|
||||
default:
|
||||
// Any response code other than NOERROR and NXDOMAIN is treated as error
|
||||
return nil, fmt.Errorf("unexpected response code '%s' for %s", dns.RcodeToString[in.Rcode], domain)
|
||||
}
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("could not find the start of authority for %s%s", fqdn, formatDNSError(in, err))
|
||||
}
|
||||
|
||||
// dnsMsgContainsCNAME checks for a CNAME answer in msg
|
||||
func dnsMsgContainsCNAME(msg *dns.Msg) bool {
|
||||
for _, ans := range msg.Answer {
|
||||
if _, ok := ans.(*dns.CNAME); ok {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func dnsQuery(fqdn string, rtype uint16, nameservers []string, recursive bool) (*dns.Msg, error) {
|
||||
m := createDNSMsg(fqdn, rtype, recursive)
|
||||
var in *dns.Msg
|
||||
var err error
|
||||
for _, ns := range nameservers {
|
||||
in, err = sendDNSQuery(m, ns)
|
||||
if err == nil && len(in.Answer) > 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
return in, err
|
||||
}
|
||||
|
||||
func createDNSMsg(fqdn string, rtype uint16, recursive bool) *dns.Msg {
|
||||
m := new(dns.Msg)
|
||||
m.SetQuestion(fqdn, rtype)
|
||||
m.SetEdns0(4096, false)
|
||||
if !recursive {
|
||||
m.RecursionDesired = false
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
func sendDNSQuery(m *dns.Msg, ns string) (*dns.Msg, error) {
|
||||
udp := &dns.Client{Net: "udp", Timeout: dnsTimeout}
|
||||
in, _, err := udp.Exchange(m, ns)
|
||||
// two kinds of errors we can handle by retrying with TCP:
|
||||
// truncation and timeout; see https://github.com/caddyserver/caddy/issues/3639
|
||||
truncated := in != nil && in.Truncated
|
||||
timeoutErr := err != nil && strings.Contains(err.Error(), "timeout")
|
||||
if truncated || timeoutErr {
|
||||
tcp := &dns.Client{Net: "tcp", Timeout: dnsTimeout}
|
||||
in, _, err = tcp.Exchange(m, ns)
|
||||
}
|
||||
return in, err
|
||||
}
|
||||
|
||||
func formatDNSError(msg *dns.Msg, err error) string {
|
||||
var parts []string
|
||||
if msg != nil {
|
||||
parts = append(parts, dns.RcodeToString[msg.Rcode])
|
||||
}
|
||||
if err != nil {
|
||||
parts = append(parts, err.Error())
|
||||
}
|
||||
if len(parts) > 0 {
|
||||
return ": " + strings.Join(parts, " ")
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// soaCacheEntry holds a cached SOA record (only selected fields)
|
||||
type soaCacheEntry struct {
|
||||
zone string // zone apex (a domain name)
|
||||
primaryNs string // primary nameserver for the zone apex
|
||||
expires time.Time // time when this cache entry should be evicted
|
||||
}
|
||||
|
||||
func newSoaCacheEntry(soa *dns.SOA) *soaCacheEntry {
|
||||
return &soaCacheEntry{
|
||||
zone: soa.Hdr.Name,
|
||||
primaryNs: soa.Ns,
|
||||
expires: time.Now().Add(time.Duration(soa.Refresh) * time.Second),
|
||||
}
|
||||
}
|
||||
|
||||
// isExpired checks whether a cache entry should be considered expired.
|
||||
func (cache *soaCacheEntry) isExpired() bool {
|
||||
return time.Now().After(cache.expires)
|
||||
}
|
||||
|
||||
// systemOrDefaultNameservers attempts to get system nameservers from the
|
||||
// resolv.conf file given by path before falling back to hard-coded defaults.
|
||||
func systemOrDefaultNameservers(path string, defaults []string) []string {
|
||||
config, err := dns.ClientConfigFromFile(path)
|
||||
if err != nil || len(config.Servers) == 0 {
|
||||
return defaults
|
||||
}
|
||||
return config.Servers
|
||||
}
|
||||
|
||||
// populateNameserverPorts ensures that all nameservers have a port number.
|
||||
func populateNameserverPorts(servers []string) {
|
||||
for i := range servers {
|
||||
_, port, _ := net.SplitHostPort(servers[i])
|
||||
if port == "" {
|
||||
servers[i] = net.JoinHostPort(servers[i], "53")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// checkDNSPropagation checks if the expected TXT record has been propagated to all authoritative nameservers.
|
||||
func checkDNSPropagation(fqdn, value string, resolvers []string) (bool, error) {
|
||||
if !strings.HasSuffix(fqdn, ".") {
|
||||
fqdn += "."
|
||||
}
|
||||
|
||||
// Initial attempt to resolve at the recursive NS
|
||||
r, err := dnsQuery(fqdn, dns.TypeTXT, resolvers, true)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
// TODO: make this configurable, maybe
|
||||
// if !p.requireCompletePropagation {
|
||||
// return true, nil
|
||||
// }
|
||||
|
||||
if r.Rcode == dns.RcodeSuccess {
|
||||
fqdn = updateDomainWithCName(r, fqdn)
|
||||
}
|
||||
|
||||
authoritativeNss, err := lookupNameservers(fqdn, resolvers)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return checkAuthoritativeNss(fqdn, value, authoritativeNss)
|
||||
}
|
||||
|
||||
// checkAuthoritativeNss queries each of the given nameservers for the expected TXT record.
|
||||
func checkAuthoritativeNss(fqdn, value string, nameservers []string) (bool, error) {
|
||||
for _, ns := range nameservers {
|
||||
r, err := dnsQuery(fqdn, dns.TypeTXT, []string{net.JoinHostPort(ns, "53")}, false)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if r.Rcode != dns.RcodeSuccess {
|
||||
if r.Rcode == dns.RcodeNameError {
|
||||
// if Present() succeeded, then it must show up eventually, or else
|
||||
// something is really broken in the DNS provider or their API;
|
||||
// no need for error here, simply have the caller try again
|
||||
return false, nil
|
||||
}
|
||||
return false, fmt.Errorf("NS %s returned %s for %s", ns, dns.RcodeToString[r.Rcode], fqdn)
|
||||
}
|
||||
|
||||
var found bool
|
||||
for _, rr := range r.Answer {
|
||||
if txt, ok := rr.(*dns.TXT); ok {
|
||||
record := strings.Join(txt.Txt, "")
|
||||
if record == value {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// lookupNameservers returns the authoritative nameservers for the given fqdn.
|
||||
func lookupNameservers(fqdn string, resolvers []string) ([]string, error) {
|
||||
var authoritativeNss []string
|
||||
|
||||
zone, err := findZoneByFQDN(fqdn, resolvers)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not determine the zone: %w", err)
|
||||
}
|
||||
|
||||
r, err := dnsQuery(zone, dns.TypeNS, resolvers, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, rr := range r.Answer {
|
||||
if ns, ok := rr.(*dns.NS); ok {
|
||||
authoritativeNss = append(authoritativeNss, strings.ToLower(ns.Ns))
|
||||
}
|
||||
}
|
||||
|
||||
if len(authoritativeNss) > 0 {
|
||||
return authoritativeNss, nil
|
||||
}
|
||||
return nil, errors.New("could not determine authoritative nameservers")
|
||||
}
|
||||
|
||||
// Update FQDN with CNAME if any
|
||||
func updateDomainWithCName(r *dns.Msg, fqdn string) string {
|
||||
for _, rr := range r.Answer {
|
||||
if cn, ok := rr.(*dns.CNAME); ok {
|
||||
if cn.Hdr.Name == fqdn {
|
||||
return cn.Target
|
||||
}
|
||||
}
|
||||
}
|
||||
return fqdn
|
||||
}
|
||||
|
||||
// recursiveNameservers are used to pre-check DNS propagation. It
|
||||
// prepends user-configured nameservers (custom) to the defaults
|
||||
// obtained from resolv.conf and defaultNameservers and ensures
|
||||
// that all server addresses have a port value.
|
||||
func recursiveNameservers(custom []string) []string {
|
||||
servers := append(custom, systemOrDefaultNameservers(defaultResolvConf, defaultNameservers)...)
|
||||
populateNameserverPorts(servers)
|
||||
return servers
|
||||
}
|
||||
|
||||
var defaultNameservers = []string{
|
||||
"8.8.8.8:53",
|
||||
"8.8.4.4:53",
|
||||
"1.1.1.1:53",
|
||||
"1.0.0.1:53",
|
||||
}
|
||||
|
||||
var dnsTimeout = 10 * time.Second
|
||||
|
||||
var (
|
||||
fqdnSOACache = map[string]*soaCacheEntry{}
|
||||
fqdnSOACacheMu sync.Mutex
|
||||
)
|
||||
|
||||
const defaultResolvConf = "/etc/resolv.conf"
|
@ -15,6 +15,7 @@
|
||||
package certmagic
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
@ -124,7 +125,7 @@ func (fs *FileStorage) Filename(key string) string {
|
||||
|
||||
// Lock obtains a lock named by the given key. It blocks
|
||||
// until the lock can be obtained or an error is returned.
|
||||
func (fs *FileStorage) Lock(key string) error {
|
||||
func (fs *FileStorage) Lock(ctx context.Context, key string) error {
|
||||
filename := fs.lockFilename(key)
|
||||
|
||||
for {
|
||||
@ -168,8 +169,13 @@ func (fs *FileStorage) Lock(key string) error {
|
||||
|
||||
default:
|
||||
// lockfile exists and is not stale;
|
||||
// just wait a moment and try again
|
||||
time.Sleep(fileLockPollInterval)
|
||||
// just wait a moment and try again,
|
||||
// or return if context cancelled
|
||||
select {
|
||||
case <-time.After(fileLockPollInterval):
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -246,6 +252,14 @@ func removeLockfile(filename string) error {
|
||||
// not terminate until up to lockFreshnessInterval after
|
||||
// the lock is released.
|
||||
func keepLockfileFresh(filename string) {
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
buf := make([]byte, stackTraceBufferSize)
|
||||
buf = buf[:runtime.Stack(buf, false)]
|
||||
log.Printf("panic: active locking: %v\n%s", err, buf)
|
||||
}
|
||||
}()
|
||||
|
||||
for {
|
||||
time.Sleep(lockFreshnessInterval)
|
||||
done, err := updateLockfileFreshness(filename)
|
12
vendor/github.com/coolaj86/certmagic/go.mod
generated
vendored
Normal file
12
vendor/github.com/coolaj86/certmagic/go.mod
generated
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
module github.com/coolaj86/certmagic
|
||||
|
||||
go 1.14
|
||||
|
||||
require (
|
||||
github.com/klauspost/cpuid v1.2.5
|
||||
github.com/libdns/libdns v0.1.0
|
||||
github.com/mholt/acmez v0.1.1
|
||||
github.com/miekg/dns v1.1.30
|
||||
go.uber.org/zap v1.15.0
|
||||
golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de
|
||||
)
|
82
vendor/github.com/coolaj86/certmagic/go.sum
generated
vendored
Normal file
82
vendor/github.com/coolaj86/certmagic/go.sum
generated
vendored
Normal file
@ -0,0 +1,82 @@
|
||||
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/klauspost/cpuid v1.2.5 h1:VBd9MyVIiJHzzgnrLQG5Bcv75H4YaWrlKqWHjurxCGo=
|
||||
github.com/klauspost/cpuid v1.2.5/go.mod h1:bYW4mA6ZgKPob1/Dlai2LviZJO7KGI3uoWLd42rAQw4=
|
||||
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/libdns/libdns v0.1.0 h1:0ctCOrVJsVzj53mop1angHp/pE3hmAhP7KiHvR0HD04=
|
||||
github.com/libdns/libdns v0.1.0/go.mod h1:yQCXzk1lEZmmCPa857bnk4TsOiqYasqpyOEeSObbb40=
|
||||
github.com/mholt/acmez v0.1.1 h1:KQODCqk+hBn3O7qfCRPj6L96uG65T5BSS95FKNEqtdA=
|
||||
github.com/mholt/acmez v0.1.1/go.mod h1:8qnn8QA/Ewx8E3ZSsmscqsIjhhpxuy9vqdgbX2ceceM=
|
||||
github.com/miekg/dns v1.1.30 h1:Qww6FseFn8PRfw07jueqIXqodm0JKiiKuK0DeXSqfyo=
|
||||
github.com/miekg/dns v1.1.30/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM=
|
||||
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
go.uber.org/atomic v1.6.0 h1:Ezj3JGmsOnG1MoRWQkPBsKLe9DwWD9QeXzTRzzldNVk=
|
||||
go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
|
||||
go.uber.org/multierr v1.5.0 h1:KCa4XfM8CWFCpxXRGok+Q0SS/0XBhMDbHHGABQLvD2A=
|
||||
go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU=
|
||||
go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee h1:0mgffUl7nfd+FpvXMVz4IDEaUSmT1ysygQC7qYo7sG4=
|
||||
go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
|
||||
go.uber.org/zap v1.15.0 h1:ZZCA22JRF2gQE5FoNmhmrf7jeJJ2uhqDUNRYKm8dvmM=
|
||||
go.uber.org/zap v1.15.0/go.mod h1:Mb2vm2krFEG5DV0W9qcHBYFtp/Wku1cvYaqPsS/WYfc=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de h1:ikNHVSjEfnvz6sxdSPCaPt572qowuyMDMJLLm3Db3ig=
|
||||
golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/lint v0.0.0-20190930215403-16217165b5de h1:5hukYrvBGR8/eNkX5mdUezrA6JiaEZDtJb9Ei+1LlBs=
|
||||
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
|
||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859 h1:R/3boaszxrf1GEUWTVDzSKVwLmSJpwZ1yqXm8j0v2QI=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190923162816-aa69164e4478 h1:l5EDrHhldLYb3ZRHDUhXF7Om7MvYXnkV9/iQNo1lX6g=
|
||||
golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200707034311-ab3426394381 h1:VXak5I6aEWmAXeQjA+QSZzlgNrpq9mjcfDemuexIKsU=
|
||||
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe h1:6fAMxZRR6sl1Uq8U61gxU+kPTs2tR8uOySCbBP7BN/M=
|
||||
golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd h1:xhmwyvizuTgC2qz7ZlMluP20uW+C3Rm0FD/WLDX8884=
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5 h1:hKsoRgsbwY1NafxrwTs+k64bikrLBkAgPir1TNCj3Zs=
|
||||
golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191216052735-49a3e744a425 h1:VvQyQJN0tSuecqgcIxMWnnfG5kSmgy9KZR9sW3W5QeA=
|
||||
golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
honnef.co/go/tools v0.0.1-2019.2.3 h1:3JgtbtFHMiCmsznwGVTUWbgGov+pVqnlf1dEJTNAXeM=
|
||||
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
@ -19,13 +19,14 @@ import (
|
||||
"crypto/tls"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/go-acme/lego/v3/challenge/tlsalpn01"
|
||||
"github.com/mholt/acmez"
|
||||
"github.com/mholt/acmez/acme"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
// GetCertificate gets a certificate to satisfy clientHello. In getting
|
||||
@ -42,7 +43,7 @@ func (cfg *Config) GetCertificate(clientHello *tls.ClientHelloInfo) (*tls.Certif
|
||||
// special case: serve up the certificate for a TLS-ALPN ACME challenge
|
||||
// (https://tools.ietf.org/html/draft-ietf-acme-tls-alpn-05)
|
||||
for _, proto := range clientHello.SupportedProtos {
|
||||
if proto == tlsalpn01.ACMETLS1Protocol {
|
||||
if proto == acmez.ACMETLS1Protocol {
|
||||
cfg.certCache.mu.RLock()
|
||||
challengeCert, ok := cfg.certCache.cache[tlsALPNCertKeyName(clientHello.ServerName)]
|
||||
cfg.certCache.mu.RUnlock()
|
||||
@ -53,15 +54,30 @@ func (cfg *Config) GetCertificate(clientHello *tls.ClientHelloInfo) (*tls.Certif
|
||||
// should already have taken care of that when we made the tls.Config)
|
||||
challengeCert, ok, err := cfg.tryDistributedChallengeSolver(clientHello)
|
||||
if err != nil {
|
||||
log.Printf("[ERROR][%s] TLS-ALPN challenge: %v", clientHello.ServerName, err)
|
||||
if cfg.Logger != nil {
|
||||
cfg.Logger.Error("tls-alpn challenge",
|
||||
zap.String("server_name", clientHello.ServerName),
|
||||
zap.Error(err))
|
||||
}
|
||||
}
|
||||
if ok {
|
||||
log.Printf("[INFO][%s] Served key authentication certificate (distributed TLS-ALPN challenge)", clientHello.ServerName)
|
||||
if cfg.Logger != nil {
|
||||
cfg.Logger.Info("served key authentication certificate",
|
||||
zap.String("server_name", clientHello.ServerName),
|
||||
zap.String("challenge", "tls-alpn-01"),
|
||||
zap.String("remote", clientHello.Conn.RemoteAddr().String()),
|
||||
zap.Bool("distributed", true))
|
||||
}
|
||||
return &challengeCert.Certificate, nil
|
||||
}
|
||||
return nil, fmt.Errorf("no certificate to complete TLS-ALPN challenge for SNI name: %s", clientHello.ServerName)
|
||||
}
|
||||
log.Printf("[INFO][%s] Served key authentication certificate (TLS-ALPN challenge)", clientHello.ServerName)
|
||||
if cfg.Logger != nil {
|
||||
cfg.Logger.Info("served key authentication certificate",
|
||||
zap.String("server_name", clientHello.ServerName),
|
||||
zap.String("challenge", "tls-alpn-01"),
|
||||
zap.String("remote", clientHello.Conn.RemoteAddr().String()))
|
||||
}
|
||||
return &challengeCert.Certificate, nil
|
||||
}
|
||||
}
|
||||
@ -221,9 +237,19 @@ func DefaultCertificateSelector(hello *tls.ClientHelloInfo, choices []Certificat
|
||||
//
|
||||
// This function is safe for concurrent use.
|
||||
func (cfg *Config) getCertDuringHandshake(hello *tls.ClientHelloInfo, loadIfNecessary, obtainIfNecessary bool) (Certificate, error) {
|
||||
log := loggerNamed(cfg.Logger, "on_demand")
|
||||
|
||||
// First check our in-memory cache to see if we've already loaded it
|
||||
cert, matched, defaulted := cfg.getCertificate(hello)
|
||||
if matched {
|
||||
if cert.managed && cfg.OnDemand != nil && obtainIfNecessary {
|
||||
// It's been reported before that if the machine goes to sleep (or
|
||||
// suspends the process) that certs which are already loaded into
|
||||
// memory won't get renewed in the background, so we need to check
|
||||
// expiry on each handshake too, sigh:
|
||||
// https://caddy.community/t/local-certificates-not-renewing-on-demand/9482
|
||||
return cfg.optionalMaintenance(log, cert, hello)
|
||||
}
|
||||
return cert, nil
|
||||
}
|
||||
|
||||
@ -237,7 +263,11 @@ func (cfg *Config) getCertDuringHandshake(hello *tls.ClientHelloInfo, loadIfNece
|
||||
if err == nil {
|
||||
loadedCert, err = cfg.handshakeMaintenance(hello, loadedCert)
|
||||
if err != nil {
|
||||
log.Printf("[ERROR] Maintaining newly-loaded certificate for %s: %v", name, err)
|
||||
if log != nil {
|
||||
log.Error("maintining newly-loaded certificate",
|
||||
zap.String("server_name", name),
|
||||
zap.Error(err))
|
||||
}
|
||||
}
|
||||
return loadedCert, nil
|
||||
}
|
||||
@ -263,6 +293,30 @@ func (cfg *Config) getCertDuringHandshake(hello *tls.ClientHelloInfo, loadIfNece
|
||||
return Certificate{}, fmt.Errorf("no certificate available for '%s'", name)
|
||||
}
|
||||
|
||||
// optionalMaintenance will perform maintenance on the certificate (if necessary) and
|
||||
// will return the resulting certificate. This should only be done if the certificate
|
||||
// is managed, OnDemand is enabled, and the scope is allowed to obtain certificates.
|
||||
func (cfg *Config) optionalMaintenance(log *zap.Logger, cert Certificate, hello *tls.ClientHelloInfo) (Certificate, error) {
|
||||
newCert, err := cfg.handshakeMaintenance(hello, cert)
|
||||
if err == nil {
|
||||
return newCert, nil
|
||||
}
|
||||
|
||||
if log != nil {
|
||||
log.Error("renewing certificate on-demand failed",
|
||||
zap.Strings("subjects", cert.Names),
|
||||
zap.Time("not_after", cert.Leaf.NotAfter),
|
||||
zap.Error(err))
|
||||
}
|
||||
|
||||
if cert.Expired() {
|
||||
return cert, err
|
||||
}
|
||||
|
||||
// still has time remaining, so serve it anyway
|
||||
return cert, nil
|
||||
}
|
||||
|
||||
// checkIfCertShouldBeObtained checks to see if an on-demand TLS certificate
|
||||
// should be obtained for a given domain based upon the config settings. If
|
||||
// a non-nil error is returned, do not issue a new certificate for name.
|
||||
@ -289,6 +343,8 @@ func (cfg *Config) checkIfCertShouldBeObtained(name string) error {
|
||||
//
|
||||
// This function is safe for use by multiple concurrent goroutines.
|
||||
func (cfg *Config) obtainOnDemandCertificate(hello *tls.ClientHelloInfo) (Certificate, error) {
|
||||
log := loggerNamed(cfg.Logger, "on_demand")
|
||||
|
||||
name := cfg.getNameFromClientHello(hello)
|
||||
|
||||
// We must protect this process from happening concurrently, so synchronize.
|
||||
@ -309,7 +365,9 @@ func (cfg *Config) obtainOnDemandCertificate(hello *tls.ClientHelloInfo) (Certif
|
||||
obtainCertWaitChansMu.Unlock()
|
||||
|
||||
// obtain the certificate
|
||||
log.Printf("[INFO] Obtaining new certificate for %s", name)
|
||||
if log != nil {
|
||||
log.Info("obtaining new certificate", zap.String("server_name", name))
|
||||
}
|
||||
// TODO: use a proper context; we use one with timeout because retries are enabled because interactive is false
|
||||
ctx, cancel := context.WithTimeout(context.TODO(), 90*time.Second)
|
||||
defer cancel()
|
||||
@ -334,13 +392,21 @@ func (cfg *Config) obtainOnDemandCertificate(hello *tls.ClientHelloInfo) (Certif
|
||||
}
|
||||
|
||||
// handshakeMaintenance performs a check on cert for expiration and OCSP validity.
|
||||
// If necessary, it will renew the certificate and/or refresh the OCSP staple.
|
||||
// OCSP stapling errors are not returned, only logged.
|
||||
//
|
||||
// This function is safe for use by multiple concurrent goroutines.
|
||||
func (cfg *Config) handshakeMaintenance(hello *tls.ClientHelloInfo, cert Certificate) (Certificate, error) {
|
||||
log := loggerNamed(cfg.Logger, "on_demand")
|
||||
|
||||
// Check cert expiration
|
||||
timeLeft := cert.Leaf.NotAfter.Sub(time.Now().UTC())
|
||||
if currentlyInRenewalWindow(cert.Leaf.NotBefore, cert.Leaf.NotAfter, cfg.RenewalWindowRatio) {
|
||||
log.Printf("[INFO] Certificate for %v expires in %s; attempting renewal", cert.Names, timeLeft)
|
||||
if log != nil {
|
||||
log.Info("certificate expires soon; attempting renewal",
|
||||
zap.Strings("identifiers", cert.Names),
|
||||
zap.Duration("remaining", timeLeft))
|
||||
}
|
||||
return cfg.renewDynamicCertificate(hello, cert)
|
||||
}
|
||||
|
||||
@ -352,7 +418,11 @@ func (cfg *Config) handshakeMaintenance(hello *tls.ClientHelloInfo, cert Certifi
|
||||
if err != nil {
|
||||
// An error with OCSP stapling is not the end of the world, and in fact, is
|
||||
// quite common considering not all certs have issuer URLs that support it.
|
||||
log.Printf("[ERROR] Getting OCSP for %s: %v", hello.ServerName, err)
|
||||
if log != nil {
|
||||
log.Warn("stapling OCSP",
|
||||
zap.String("server_name", hello.ServerName),
|
||||
zap.Error(err))
|
||||
}
|
||||
}
|
||||
cfg.certCache.mu.Lock()
|
||||
cfg.certCache.cache[cert.hash] = cert
|
||||
@ -370,6 +440,8 @@ func (cfg *Config) handshakeMaintenance(hello *tls.ClientHelloInfo, cert Certifi
|
||||
//
|
||||
// This function is safe for use by multiple concurrent goroutines.
|
||||
func (cfg *Config) renewDynamicCertificate(hello *tls.ClientHelloInfo, currentCert Certificate) (Certificate, error) {
|
||||
log := loggerNamed(cfg.Logger, "on_demand")
|
||||
|
||||
name := cfg.getNameFromClientHello(hello)
|
||||
|
||||
obtainCertWaitChansMu.Lock()
|
||||
@ -398,7 +470,9 @@ func (cfg *Config) renewDynamicCertificate(hello *tls.ClientHelloInfo, currentCe
|
||||
}
|
||||
|
||||
// renew and reload the certificate
|
||||
log.Printf("[INFO] Renewing certificate for %s", name)
|
||||
if log != nil {
|
||||
log.Info("renewing certificate", zap.String("server_name", name))
|
||||
}
|
||||
// TODO: use a proper context; we use one with timeout because retries are enabled because interactive is false
|
||||
ctx, cancel := context.WithTimeout(context.TODO(), 90*time.Second)
|
||||
defer cancel()
|
||||
@ -409,7 +483,9 @@ func (cfg *Config) renewDynamicCertificate(hello *tls.ClientHelloInfo, currentCe
|
||||
// make the replacement as atomic as possible.
|
||||
newCert, err := cfg.CacheManagedCertificate(name)
|
||||
if err != nil {
|
||||
log.Printf("[ERROR] loading renewed certificate for %s: %v", name, err)
|
||||
if log != nil {
|
||||
log.Error("loading renewed certificate", zap.String("server_name", name), zap.Error(err))
|
||||
}
|
||||
} else {
|
||||
// replace the old certificate with the new one
|
||||
cfg.certCache.replaceCertificate(currentCert, newCert)
|
||||
@ -451,13 +527,13 @@ func (cfg *Config) tryDistributedChallengeSolver(clientHello *tls.ClientHelloInf
|
||||
return Certificate{}, false, fmt.Errorf("opening distributed challenge token file %s: %v", tokenKey, err)
|
||||
}
|
||||
|
||||
var chalInfo challengeInfo
|
||||
var chalInfo acme.Challenge
|
||||
err = json.Unmarshal(chalInfoBytes, &chalInfo)
|
||||
if err != nil {
|
||||
return Certificate{}, false, fmt.Errorf("decoding challenge token file %s (corrupted?): %v", tokenKey, err)
|
||||
}
|
||||
|
||||
cert, err := tlsalpn01.ChallengeCert(chalInfo.Domain, chalInfo.KeyAuth)
|
||||
cert, err := acmez.TLSALPN01ChallengeCert(chalInfo)
|
||||
if err != nil {
|
||||
return Certificate{}, false, fmt.Errorf("making TLS-ALPN challenge certificate: %v", err)
|
||||
}
|
@ -16,11 +16,11 @@ package certmagic
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"log"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/go-acme/lego/v3/challenge/http01"
|
||||
"github.com/mholt/acmez/acme"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
// HTTPChallengeHandler wraps h in a handler that can solve the ACME
|
||||
@ -78,33 +78,47 @@ func (am *ACMEManager) distributedHTTPChallengeSolver(w http.ResponseWriter, r *
|
||||
chalInfoBytes, err := am.config.Storage.Load(tokenKey)
|
||||
if err != nil {
|
||||
if _, ok := err.(ErrNotExist); !ok {
|
||||
log.Printf("[ERROR][%s] Opening distributed HTTP challenge token file: %v", host, err)
|
||||
if am.Logger != nil {
|
||||
am.Logger.Error("opening distributed HTTP challenge token file",
|
||||
zap.String("host", host),
|
||||
zap.Error(err))
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
var chalInfo challengeInfo
|
||||
err = json.Unmarshal(chalInfoBytes, &chalInfo)
|
||||
var challenge acme.Challenge
|
||||
err = json.Unmarshal(chalInfoBytes, &challenge)
|
||||
if err != nil {
|
||||
log.Printf("[ERROR][%s] Decoding challenge token file %s (corrupted?): %v", host, tokenKey, err)
|
||||
if am.Logger != nil {
|
||||
am.Logger.Error("decoding HTTP challenge token file (corrupted?)",
|
||||
zap.String("host", host),
|
||||
zap.String("token_key", tokenKey),
|
||||
zap.Error(err))
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
return answerHTTPChallenge(w, r, chalInfo)
|
||||
return am.answerHTTPChallenge(w, r, challenge)
|
||||
}
|
||||
|
||||
// answerHTTPChallenge solves the challenge with chalInfo.
|
||||
// Most of this code borrowed from xenolf/lego's built-in HTTP-01
|
||||
// Most of this code borrowed from xenolf's built-in HTTP-01
|
||||
// challenge solver in March 2018.
|
||||
func answerHTTPChallenge(w http.ResponseWriter, r *http.Request, chalInfo challengeInfo) bool {
|
||||
challengeReqPath := http01.ChallengePath(chalInfo.Token)
|
||||
func (am *ACMEManager) answerHTTPChallenge(w http.ResponseWriter, r *http.Request, challenge acme.Challenge) bool {
|
||||
challengeReqPath := challenge.HTTP01ResourcePath()
|
||||
if r.URL.Path == challengeReqPath &&
|
||||
strings.EqualFold(hostOnly(r.Host), chalInfo.Domain) && // mitigate DNS rebinding attacks
|
||||
strings.EqualFold(hostOnly(r.Host), challenge.Identifier.Value) && // mitigate DNS rebinding attacks
|
||||
r.Method == "GET" {
|
||||
w.Header().Add("Content-Type", "text/plain")
|
||||
w.Write([]byte(chalInfo.KeyAuth))
|
||||
w.Write([]byte(challenge.KeyAuthorization))
|
||||
r.Close = true
|
||||
log.Printf("[INFO][%s] Served key authentication (HTTP challenge)", chalInfo.Domain)
|
||||
if am.Logger != nil {
|
||||
am.Logger.Info("served key authentication",
|
||||
zap.String("identifier", challenge.Identifier.Value),
|
||||
zap.String("challenge", "http-01"),
|
||||
zap.String("remote", r.RemoteAddr))
|
||||
}
|
||||
return true
|
||||
}
|
||||
return false
|
@ -21,9 +21,11 @@ import (
|
||||
"fmt"
|
||||
"log"
|
||||
"path"
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"go.uber.org/zap"
|
||||
"golang.org/x/crypto/ocsp"
|
||||
)
|
||||
|
||||
@ -31,12 +33,35 @@ import (
|
||||
// that loops indefinitely and, on a regular schedule, checks
|
||||
// certificates for expiration and initiates a renewal of certs
|
||||
// that are expiring soon. It also updates OCSP stapling. It
|
||||
// should only be called once per cache.
|
||||
func (certCache *Cache) maintainAssets() {
|
||||
// should only be called once per cache. Panics are recovered,
|
||||
// and if panicCount < 10, the function is called recursively,
|
||||
// incrementing panicCount each time. Initial invocation should
|
||||
// start panicCount at 0.
|
||||
func (certCache *Cache) maintainAssets(panicCount int) {
|
||||
log := loggerNamed(certCache.logger, "maintenance")
|
||||
if log != nil {
|
||||
log = log.With(zap.String("cache", fmt.Sprintf("%p", certCache)))
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
buf := make([]byte, stackTraceBufferSize)
|
||||
buf = buf[:runtime.Stack(buf, false)]
|
||||
if log != nil {
|
||||
log.Error("panic", zap.Any("error", err), zap.ByteString("stack", buf))
|
||||
}
|
||||
if panicCount < 10 {
|
||||
certCache.maintainAssets(panicCount + 1)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
renewalTicker := time.NewTicker(certCache.options.RenewCheckInterval)
|
||||
ocspTicker := time.NewTicker(certCache.options.OCSPCheckInterval)
|
||||
|
||||
log.Printf("[INFO][cache:%p] Started certificate maintenance routine", certCache)
|
||||
if log != nil {
|
||||
log.Info("started background certificate maintenance")
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
@ -45,8 +70,8 @@ func (certCache *Cache) maintainAssets() {
|
||||
select {
|
||||
case <-renewalTicker.C:
|
||||
err := certCache.RenewManagedCertificates(ctx)
|
||||
if err != nil {
|
||||
log.Printf("[ERROR][cache:%p] Renewing managed certificates: %v", certCache, err)
|
||||
if err != nil && log != nil {
|
||||
log.Error("renewing managed certificates", zap.Error(err))
|
||||
}
|
||||
case <-ocspTicker.C:
|
||||
certCache.updateOCSPStaples(ctx)
|
||||
@ -54,7 +79,9 @@ func (certCache *Cache) maintainAssets() {
|
||||
renewalTicker.Stop()
|
||||
ocspTicker.Stop()
|
||||
// TODO: stop any in-progress maintenance operations and clear locks we made (this might be done now with our use of context)
|
||||
log.Printf("[INFO][cache:%p] Stopped certificate maintenance routine", certCache)
|
||||
if log != nil {
|
||||
log.Info("stopped background certificate maintenance")
|
||||
}
|
||||
close(certCache.doneChan)
|
||||
return
|
||||
}
|
||||
@ -67,6 +94,8 @@ func (certCache *Cache) maintainAssets() {
|
||||
// need to call this. This method assumes non-interactive
|
||||
// mode (i.e. operating in the background).
|
||||
func (certCache *Cache) RenewManagedCertificates(ctx context.Context) error {
|
||||
log := loggerNamed(certCache.logger, "maintenance")
|
||||
|
||||
// configs will hold a map of certificate name to the config
|
||||
// to use when managing that certificate
|
||||
configs := make(map[string]*Config)
|
||||
@ -87,7 +116,9 @@ func (certCache *Cache) RenewManagedCertificates(ctx context.Context) error {
|
||||
|
||||
// the list of names on this cert should never be empty... programmer error?
|
||||
if cert.Names == nil || len(cert.Names) == 0 {
|
||||
log.Printf("[WARNING] Certificate keyed by '%s' has no names: %v - removing from cache", certKey, cert.Names)
|
||||
if log != nil {
|
||||
log.Warn("certificate has no names; removing from cache", zap.String("cert_key", certKey))
|
||||
}
|
||||
deleteQueue = append(deleteQueue, cert)
|
||||
continue
|
||||
}
|
||||
@ -95,12 +126,19 @@ func (certCache *Cache) RenewManagedCertificates(ctx context.Context) error {
|
||||
// get the config associated with this certificate
|
||||
cfg, err := certCache.getConfig(cert)
|
||||
if err != nil {
|
||||
log.Printf("[ERROR] Getting configuration to manage certificate for names %v; unable to renew: %v", cert.Names, err)
|
||||
if log != nil {
|
||||
log.Error("unable to get configuration to manage certificate; unable to renew",
|
||||
zap.Strings("identifiers", cert.Names),
|
||||
zap.Error(err))
|
||||
}
|
||||
continue
|
||||
}
|
||||
if cfg == nil {
|
||||
// this is bad if this happens, probably a programmer error (oops)
|
||||
log.Printf("[ERROR] No configuration associated with certificate for names %v; unable to manage", cert.Names)
|
||||
if log != nil {
|
||||
log.Error("no configuration associated with certificate; unable to manage",
|
||||
zap.Strings("identifiers", cert.Names))
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
@ -115,8 +153,11 @@ func (certCache *Cache) RenewManagedCertificates(ctx context.Context) error {
|
||||
storedCertExpiring, err := cfg.managedCertInStorageExpiresSoon(cert)
|
||||
if err != nil {
|
||||
// hmm, weird, but not a big deal, maybe it was deleted or something
|
||||
log.Printf("[NOTICE] Error while checking if certificate for %v in storage is also expiring soon: %v",
|
||||
cert.Names, err)
|
||||
if log != nil {
|
||||
log.Warn("error while checking if stored certificate is also expiring soon",
|
||||
zap.Strings("identifiers", cert.Names),
|
||||
zap.Error(err))
|
||||
}
|
||||
} else if !storedCertExpiring {
|
||||
// if the certificate is NOT expiring soon and there was no error, then we
|
||||
// are good to just reload the certificate from storage instead of repeating
|
||||
@ -137,15 +178,22 @@ func (certCache *Cache) RenewManagedCertificates(ctx context.Context) error {
|
||||
// Reload certificates that merely need to be updated in memory
|
||||
for _, oldCert := range reloadQueue {
|
||||
timeLeft := oldCert.Leaf.NotAfter.Sub(time.Now().UTC())
|
||||
log.Printf("[INFO] %v Maintenance routine: certificate expires in %s, but is already renewed in storage; reloading stored certificate",
|
||||
oldCert.Names, timeLeft)
|
||||
if log != nil {
|
||||
log.Info("certificate expires soon, but is already renewed in storage; reloading stored certificate",
|
||||
zap.Strings("identifiers", oldCert.Names),
|
||||
zap.Duration("remaining", timeLeft))
|
||||
}
|
||||
|
||||
cfg := configs[oldCert.Names[0]]
|
||||
|
||||
// crucially, this happens OUTSIDE a lock on the certCache
|
||||
err := cfg.reloadManagedCertificate(oldCert)
|
||||
if err != nil {
|
||||
log.Printf("[ERROR] Loading renewed certificate: %v", err)
|
||||
if log != nil {
|
||||
log.Error("loading renewed certificate",
|
||||
zap.Strings("identifiers", oldCert.Names),
|
||||
zap.Error(err))
|
||||
}
|
||||
continue
|
||||
}
|
||||
}
|
||||
@ -155,7 +203,11 @@ func (certCache *Cache) RenewManagedCertificates(ctx context.Context) error {
|
||||
cfg := configs[oldCert.Names[0]]
|
||||
err := certCache.queueRenewalTask(ctx, oldCert, cfg)
|
||||
if err != nil {
|
||||
log.Printf("[ERROR] %v", err)
|
||||
if log != nil {
|
||||
log.Error("queueing renewal task",
|
||||
zap.Strings("identifiers", oldCert.Names),
|
||||
zap.Error(err))
|
||||
}
|
||||
continue
|
||||
}
|
||||
}
|
||||
@ -171,8 +223,14 @@ func (certCache *Cache) RenewManagedCertificates(ctx context.Context) error {
|
||||
}
|
||||
|
||||
func (certCache *Cache) queueRenewalTask(ctx context.Context, oldCert Certificate, cfg *Config) error {
|
||||
log := loggerNamed(certCache.logger, "maintenance")
|
||||
|
||||
timeLeft := oldCert.Leaf.NotAfter.Sub(time.Now().UTC())
|
||||
log.Printf("[INFO] %v Maintenance routine: certificate expires in %v; queueing for renewal", oldCert.Names, timeLeft)
|
||||
if log != nil {
|
||||
log.Info("certificate expires soon; queuing for renewal",
|
||||
zap.Strings("identifiers", oldCert.Names),
|
||||
zap.Duration("remaining", timeLeft))
|
||||
}
|
||||
|
||||
// Get the name which we should use to renew this certificate;
|
||||
// we only support managing certificates with one name per cert,
|
||||
@ -180,9 +238,13 @@ func (certCache *Cache) queueRenewalTask(ctx context.Context, oldCert Certificat
|
||||
renewName := oldCert.Names[0]
|
||||
|
||||
// queue up this renewal job (is a no-op if already active or queued)
|
||||
jm.Submit("renew_"+renewName, func() error {
|
||||
jm.Submit(cfg.Logger, "renew_"+renewName, func() error {
|
||||
timeLeft := oldCert.Leaf.NotAfter.Sub(time.Now().UTC())
|
||||
log.Printf("[INFO] %v Maintenance routine: attempting renewal with %v remaining", oldCert.Names, timeLeft)
|
||||
if log != nil {
|
||||
log.Info("attempting certificate renewal",
|
||||
zap.Strings("identifiers", oldCert.Names),
|
||||
zap.Duration("remaining", timeLeft))
|
||||
}
|
||||
|
||||
// perform renewal - crucially, this happens OUTSIDE a lock on certCache
|
||||
err := cfg.RenewCert(ctx, renewName, false)
|
||||
@ -215,6 +277,8 @@ func (certCache *Cache) queueRenewalTask(ctx context.Context, oldCert Certificat
|
||||
// Ryan Sleevi's recommendations for good OCSP support:
|
||||
// https://gist.github.com/sleevi/5efe9ef98961ecfb4da8
|
||||
func (certCache *Cache) updateOCSPStaples(ctx context.Context) {
|
||||
log := loggerNamed(certCache.logger, "maintenance")
|
||||
|
||||
// temporary structures to store updates or tasks
|
||||
// so that we can keep our locks short-lived
|
||||
type ocspUpdate struct {
|
||||
@ -234,8 +298,8 @@ func (certCache *Cache) updateOCSPStaples(ctx context.Context) {
|
||||
// obtain brief read lock during our scan to see which staples need updating
|
||||
certCache.mu.RLock()
|
||||
for certHash, cert := range certCache.cache {
|
||||
// no point in updating OCSP for expired certificates
|
||||
if time.Now().After(cert.Leaf.NotAfter) {
|
||||
// no point in updating OCSP for expired or "synthetic" certificates
|
||||
if cert.Leaf == nil || cert.Expired() {
|
||||
continue
|
||||
}
|
||||
var lastNextUpdate time.Time
|
||||
@ -257,12 +321,19 @@ func (certCache *Cache) updateOCSPStaples(ctx context.Context) {
|
||||
|
||||
cfg, err := certCache.getConfig(cert)
|
||||
if err != nil {
|
||||
log.Printf("[ERROR] Getting configuration to manage OCSP for certificate with names %v; unable to refresh: %v", cert.Names, err)
|
||||
if log != nil {
|
||||
log.Error("unable to refresh OCSP staple because getting automation config for certificate failed",
|
||||
zap.Strings("identifiers", cert.Names),
|
||||
zap.Error(err))
|
||||
}
|
||||
continue
|
||||
}
|
||||
if cfg == nil {
|
||||
// this is bad if this happens, probably a programmer error (oops)
|
||||
log.Printf("[ERROR] No configuration associated with certificate for names %v; unable to manage OCSP", cert.Names)
|
||||
if log != nil {
|
||||
log.Error("no configuration associated with certificate; unable to manage OCSP staples",
|
||||
zap.Strings("identifiers", cert.Names))
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
@ -270,7 +341,11 @@ func (certCache *Cache) updateOCSPStaples(ctx context.Context) {
|
||||
if err != nil {
|
||||
if cert.ocsp != nil {
|
||||
// if there was no staple before, that's fine; otherwise we should log the error
|
||||
log.Printf("[ERROR] Checking OCSP: %v", err)
|
||||
if log != nil {
|
||||
log.Error("stapling OCSP",
|
||||
zap.Strings("identifiers", cert.Names),
|
||||
zap.Error(err))
|
||||
}
|
||||
}
|
||||
continue
|
||||
}
|
||||
@ -279,8 +354,12 @@ func (certCache *Cache) updateOCSPStaples(ctx context.Context) {
|
||||
// If there was no staple before, or if the response is updated, make
|
||||
// sure we apply the update to all names on the certificate.
|
||||
if cert.ocsp != nil && (lastNextUpdate.IsZero() || lastNextUpdate != cert.ocsp.NextUpdate) {
|
||||
log.Printf("[INFO] Advancing OCSP staple for %v from %s to %s",
|
||||
cert.Names, lastNextUpdate, cert.ocsp.NextUpdate)
|
||||
if log != nil {
|
||||
log.Info("advancing OCSP staple",
|
||||
zap.Strings("identifiers", cert.Names),
|
||||
zap.Time("from", lastNextUpdate),
|
||||
zap.Time("to", cert.ocsp.NextUpdate))
|
||||
}
|
||||
updated[certHash] = ocspUpdate{rawBytes: cert.Certificate.OCSPStaple, parsed: cert.ocsp}
|
||||
}
|
||||
|
||||
@ -305,8 +384,11 @@ func (certCache *Cache) updateOCSPStaples(ctx context.Context) {
|
||||
// We attempt to replace any certificates that were revoked.
|
||||
// Crucially, this happens OUTSIDE a lock on the certCache.
|
||||
for _, oldCert := range renewQueue {
|
||||
log.Printf("[INFO] OCSP status for managed certificate %v (expiration=%s) is REVOKED; attempting to replace with new certificate",
|
||||
oldCert.Names, oldCert.Leaf.NotAfter)
|
||||
if log != nil {
|
||||
log.Warn("OCSP status for managed certificate is REVOKED; attempting to replace with new certificate",
|
||||
zap.Strings("identifiers", oldCert.Names),
|
||||
zap.Time("expiration", oldCert.Leaf.NotAfter))
|
||||
}
|
||||
|
||||
renewName := oldCert.Names[0]
|
||||
cfg := configs[renewName]
|
||||
@ -315,7 +397,11 @@ func (certCache *Cache) updateOCSPStaples(ctx context.Context) {
|
||||
err := cfg.RenewCert(ctx, renewName, false)
|
||||
if err != nil {
|
||||
// probably better to not serve a revoked certificate at all
|
||||
log.Printf("[ERROR] Obtaining new certificate for %v due to OCSP status of revoked: %v; removing from cache", oldCert.Names, err)
|
||||
if log != nil {
|
||||
log.Error("unable to obtain new to certificate after OCSP status of REVOKED; removing from cache",
|
||||
zap.Strings("identifiers", oldCert.Names),
|
||||
zap.Error(err))
|
||||
}
|
||||
certCache.mu.Lock()
|
||||
certCache.removeCertificate(oldCert)
|
||||
certCache.mu.Unlock()
|
||||
@ -323,7 +409,11 @@ func (certCache *Cache) updateOCSPStaples(ctx context.Context) {
|
||||
}
|
||||
err = cfg.reloadManagedCertificate(oldCert)
|
||||
if err != nil {
|
||||
log.Printf("[ERROR] After obtaining new certificate due to OCSP status of revoked: %v", err)
|
||||
if log != nil {
|
||||
log.Error("after obtaining new certificate due to OCSP status of REVOKED",
|
||||
zap.Strings("identifiers", oldCert.Names),
|
||||
zap.Error(err))
|
||||
}
|
||||
continue
|
||||
}
|
||||
}
|
||||
@ -338,15 +428,15 @@ type CleanStorageOptions struct {
|
||||
|
||||
// CleanStorage removes assets which are no longer useful,
|
||||
// according to opts.
|
||||
func CleanStorage(storage Storage, opts CleanStorageOptions) {
|
||||
func CleanStorage(ctx context.Context, storage Storage, opts CleanStorageOptions) {
|
||||
if opts.OCSPStaples {
|
||||
err := deleteOldOCSPStaples(storage)
|
||||
err := deleteOldOCSPStaples(ctx, storage)
|
||||
if err != nil {
|
||||
log.Printf("[ERROR] Deleting old OCSP staples: %v", err)
|
||||
}
|
||||
}
|
||||
if opts.ExpiredCerts {
|
||||
err := deleteExpiredCerts(storage, opts.ExpiredCertGracePeriod)
|
||||
err := deleteExpiredCerts(ctx, storage, opts.ExpiredCertGracePeriod)
|
||||
if err != nil {
|
||||
log.Printf("[ERROR] Deleting expired certificates: %v", err)
|
||||
}
|
||||
@ -354,13 +444,19 @@ func CleanStorage(storage Storage, opts CleanStorageOptions) {
|
||||
// TODO: delete stale locks?
|
||||
}
|
||||
|
||||
func deleteOldOCSPStaples(storage Storage) error {
|
||||
func deleteOldOCSPStaples(ctx context.Context, storage Storage) error {
|
||||
ocspKeys, err := storage.List(prefixOCSP, false)
|
||||
if err != nil {
|
||||
// maybe just hasn't been created yet; no big deal
|
||||
return nil
|
||||
}
|
||||
for _, key := range ocspKeys {
|
||||
// if context was cancelled, quit early; otherwise proceed
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
default:
|
||||
}
|
||||
ocspBytes, err := storage.Load(key)
|
||||
if err != nil {
|
||||
log.Printf("[ERROR] While deleting old OCSP staples, unable to load staple file: %v", err)
|
||||
@ -386,7 +482,7 @@ func deleteOldOCSPStaples(storage Storage) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func deleteExpiredCerts(storage Storage, gracePeriod time.Duration) error {
|
||||
func deleteExpiredCerts(ctx context.Context, storage Storage, gracePeriod time.Duration) error {
|
||||
issuerKeys, err := storage.List(prefixCerts, false)
|
||||
if err != nil {
|
||||
// maybe just hasn't been created yet; no big deal
|
||||
@ -401,6 +497,13 @@ func deleteExpiredCerts(storage Storage, gracePeriod time.Duration) error {
|
||||
}
|
||||
|
||||
for _, siteKey := range siteKeys {
|
||||
// if context was cancelled, quit early; otherwise proceed
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
default:
|
||||
}
|
||||
|
||||
siteAssets, err := storage.List(siteKey, false)
|
||||
if err != nil {
|
||||
log.Printf("[ERROR] Listing contents of %s: %v", siteKey, err)
|
@ -124,7 +124,7 @@ func stapleOCSP(storage Storage, cert *Certificate, pemBundle []byte) (*ocsp.Res
|
||||
// IssuingCertificateURL in the certificate. If the []byte and/or ocsp.Response return
|
||||
// values are nil, the OCSP status may be assumed OCSPUnknown.
|
||||
//
|
||||
// Borrowed from github.com/go-acme/lego
|
||||
// Borrowed from xenolf.
|
||||
func getOCSPForCert(bundle []byte) ([]byte, *ocsp.Response, error) {
|
||||
// TODO: Perhaps this should be synchronized too, with a Locker?
|
||||
|
@ -1,7 +1,23 @@
|
||||
// Copyright 2015 Matthew Holt
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package certmagic
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
"runtime"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
@ -51,6 +67,14 @@ func (r *RingBufferRateLimiter) Stop() {
|
||||
}
|
||||
|
||||
func (r *RingBufferRateLimiter) loop() {
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
buf := make([]byte, stackTraceBufferSize)
|
||||
buf = buf[:runtime.Stack(buf, false)]
|
||||
log.Printf("panic: ring buffer rate limiter: %v\n%s", err, buf)
|
||||
}
|
||||
}()
|
||||
|
||||
for {
|
||||
// if we've been stopped, return
|
||||
select {
|
@ -15,6 +15,7 @@
|
||||
package certmagic
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
@ -22,13 +23,15 @@ import (
|
||||
"net"
|
||||
"net/http"
|
||||
"path"
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/go-acme/lego/v3/challenge"
|
||||
"github.com/go-acme/lego/v3/challenge/tlsalpn01"
|
||||
"github.com/libdns/libdns"
|
||||
"github.com/mholt/acmez"
|
||||
"github.com/mholt/acmez/acme"
|
||||
)
|
||||
|
||||
// httpSolver solves the HTTP challenge. It must be
|
||||
@ -48,7 +51,7 @@ type httpSolver struct {
|
||||
}
|
||||
|
||||
// Present starts an HTTP server if none is already listening on s.address.
|
||||
func (s *httpSolver) Present(domain, token, keyAuth string) error {
|
||||
func (s *httpSolver) Present(ctx context.Context, _ acme.Challenge) error {
|
||||
solversMu.Lock()
|
||||
defer solversMu.Unlock()
|
||||
|
||||
@ -75,6 +78,13 @@ func (s *httpSolver) Present(domain, token, keyAuth string) error {
|
||||
|
||||
// serve is an HTTP server that serves only HTTP challenge responses.
|
||||
func (s *httpSolver) serve(si *solverInfo) {
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
buf := make([]byte, stackTraceBufferSize)
|
||||
buf = buf[:runtime.Stack(buf, false)]
|
||||
log.Printf("panic: http solver server: %v\n%s", err, buf)
|
||||
}
|
||||
}()
|
||||
defer close(si.done)
|
||||
httpServer := &http.Server{Handler: s.acmeManager.HTTPChallengeHandler(http.NewServeMux())}
|
||||
httpServer.SetKeepAlivesEnabled(false)
|
||||
@ -85,7 +95,7 @@ func (s *httpSolver) serve(si *solverInfo) {
|
||||
}
|
||||
|
||||
// CleanUp cleans up the HTTP server if it is the last one to finish.
|
||||
func (s *httpSolver) CleanUp(domain, token, keyAuth string) error {
|
||||
func (s *httpSolver) CleanUp(ctx context.Context, _ acme.Challenge) error {
|
||||
solversMu.Lock()
|
||||
defer solversMu.Unlock()
|
||||
si := getSolverInfo(s.address)
|
||||
@ -112,20 +122,20 @@ type tlsALPNSolver struct {
|
||||
|
||||
// Present adds the certificate to the certificate cache and, if
|
||||
// needed, starts a TLS server for answering TLS-ALPN challenges.
|
||||
func (s *tlsALPNSolver) Present(domain, token, keyAuth string) error {
|
||||
func (s *tlsALPNSolver) Present(ctx context.Context, chal acme.Challenge) error {
|
||||
// load the certificate into the cache; this isn't strictly necessary
|
||||
// if we're using the distributed solver since our GetCertificate
|
||||
// function will check storage for the keyAuth anyway, but it seems
|
||||
// like loading it into the cache is the right thing to do
|
||||
cert, err := tlsalpn01.ChallengeCert(domain, keyAuth)
|
||||
cert, err := acmez.TLSALPN01ChallengeCert(chal)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
certHash := hashCertificateChain(cert.Certificate)
|
||||
s.config.certCache.mu.Lock()
|
||||
s.config.certCache.cache[tlsALPNCertKeyName(domain)] = Certificate{
|
||||
s.config.certCache.cache[tlsALPNCertKeyName(chal.Identifier.Value)] = Certificate{
|
||||
Certificate: *cert,
|
||||
Names: []string{domain},
|
||||
Names: []string{chal.Identifier.Value},
|
||||
hash: certHash, // perhaps not necesssary
|
||||
}
|
||||
s.config.certCache.mu.Unlock()
|
||||
@ -159,6 +169,13 @@ func (s *tlsALPNSolver) Present(domain, token, keyAuth string) error {
|
||||
si.listener = tls.NewListener(ln, s.config.TLSConfig())
|
||||
|
||||
go func() {
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
buf := make([]byte, stackTraceBufferSize)
|
||||
buf = buf[:runtime.Stack(buf, false)]
|
||||
log.Printf("panic: tls-alpn solver server: %v\n%s", err, buf)
|
||||
}
|
||||
}()
|
||||
defer close(si.done)
|
||||
for {
|
||||
conn, err := si.listener.Accept()
|
||||
@ -178,6 +195,13 @@ func (s *tlsALPNSolver) Present(domain, token, keyAuth string) error {
|
||||
|
||||
// handleConn completes the TLS handshake and then closes conn.
|
||||
func (*tlsALPNSolver) handleConn(conn net.Conn) {
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
buf := make([]byte, stackTraceBufferSize)
|
||||
buf = buf[:runtime.Stack(buf, false)]
|
||||
log.Printf("panic: tls-alpn solver handler: %v\n%s", err, buf)
|
||||
}
|
||||
}()
|
||||
defer conn.Close()
|
||||
tlsConn, ok := conn.(*tls.Conn)
|
||||
if !ok {
|
||||
@ -193,9 +217,9 @@ func (*tlsALPNSolver) handleConn(conn net.Conn) {
|
||||
|
||||
// CleanUp removes the challenge certificate from the cache, and if
|
||||
// it is the last one to finish, stops the TLS server.
|
||||
func (s *tlsALPNSolver) CleanUp(domain, token, keyAuth string) error {
|
||||
func (s *tlsALPNSolver) CleanUp(ctx context.Context, chal acme.Challenge) error {
|
||||
s.config.certCache.mu.Lock()
|
||||
delete(s.config.certCache.cache, tlsALPNCertKeyName(domain))
|
||||
delete(s.config.certCache.cache, tlsALPNCertKeyName(chal.Identifier.Value))
|
||||
s.config.certCache.mu.Unlock()
|
||||
|
||||
solversMu.Lock()
|
||||
@ -223,6 +247,195 @@ func tlsALPNCertKeyName(sniName string) string {
|
||||
return sniName + ":acme-tls-alpn"
|
||||
}
|
||||
|
||||
// DNS01Solver is a type that makes libdns providers usable
|
||||
// as ACME dns-01 challenge solvers.
|
||||
// See https://github.com/libdns/libdns
|
||||
type DNS01Solver struct {
|
||||
// The implementation that interacts with the DNS
|
||||
// provider to set or delete records. (REQUIRED)
|
||||
DNSProvider ACMEDNSProvider
|
||||
|
||||
// The TTL for the temporary challenge records.
|
||||
TTL time.Duration
|
||||
|
||||
// Maximum time to wait for temporary record to appear.
|
||||
PropagationTimeout time.Duration
|
||||
|
||||
// Preferred DNS resolver(s) to use when doing DNS lookups.
|
||||
Resolvers []string
|
||||
|
||||
txtRecords map[string]dnsPresentMemory // keyed by domain name
|
||||
txtRecordsMu sync.Mutex
|
||||
}
|
||||
|
||||
// Present creates the DNS TXT record for the given ACME challenge.
|
||||
func (s *DNS01Solver) Present(ctx context.Context, challenge acme.Challenge) error {
|
||||
dnsName := challenge.DNS01TXTRecordName()
|
||||
keyAuth := challenge.DNS01KeyAuthorization()
|
||||
|
||||
rec := libdns.Record{
|
||||
Type: "TXT",
|
||||
Name: dnsName,
|
||||
Value: keyAuth,
|
||||
TTL: s.TTL,
|
||||
}
|
||||
|
||||
// multiple identifiers can have the same ACME challenge
|
||||
// domain (e.g. example.com and *.example.com) so we need
|
||||
// to ensure that we don't solve those concurrently and
|
||||
// step on each challenges' metaphorical toes; see
|
||||
// https://github.com/caddyserver/caddy/issues/3474
|
||||
activeDNSChallenges.Lock(dnsName)
|
||||
|
||||
zone, err := findZoneByFQDN(dnsName, recursiveNameservers(s.Resolvers))
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not determine zone for domain %q: %v", dnsName, err)
|
||||
}
|
||||
|
||||
results, err := s.DNSProvider.AppendRecords(ctx, zone, []libdns.Record{rec})
|
||||
if err != nil {
|
||||
return fmt.Errorf("adding temporary record for zone %s: %w", zone, err)
|
||||
}
|
||||
if len(results) != 1 {
|
||||
return fmt.Errorf("expected one record, got %d: %v", len(results), results)
|
||||
}
|
||||
|
||||
// remember the record and zone we got so we can clean up more efficiently
|
||||
s.txtRecordsMu.Lock()
|
||||
if s.txtRecords == nil {
|
||||
s.txtRecords = make(map[string]dnsPresentMemory)
|
||||
}
|
||||
s.txtRecords[dnsName] = dnsPresentMemory{dnsZone: zone, rec: results[0]}
|
||||
s.txtRecordsMu.Unlock()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Wait blocks until the TXT record created in Present() appears in
|
||||
// authoritative lookups, i.e. until it has propagated, or until
|
||||
// timeout, whichever is first.
|
||||
func (s *DNS01Solver) Wait(ctx context.Context, challenge acme.Challenge) error {
|
||||
dnsName := challenge.DNS01TXTRecordName()
|
||||
keyAuth := challenge.DNS01KeyAuthorization()
|
||||
|
||||
timeout := s.PropagationTimeout
|
||||
if timeout == 0 {
|
||||
timeout = 2 * time.Minute
|
||||
}
|
||||
const interval = 2 * time.Second
|
||||
|
||||
resolvers := recursiveNameservers(s.Resolvers)
|
||||
|
||||
var err error
|
||||
start := time.Now()
|
||||
for time.Since(start) < timeout {
|
||||
select {
|
||||
case <-time.After(interval):
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
}
|
||||
var ready bool
|
||||
ready, err = checkDNSPropagation(dnsName, keyAuth, resolvers)
|
||||
if err != nil {
|
||||
return fmt.Errorf("checking DNS propagation of %s: %w", dnsName, err)
|
||||
}
|
||||
if ready {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
return fmt.Errorf("timed out waiting for record to fully propagate; verify DNS provider configuration is correct - last error: %v", err)
|
||||
}
|
||||
|
||||
// CleanUp deletes the DNS TXT record created in Present().
|
||||
func (s *DNS01Solver) CleanUp(ctx context.Context, challenge acme.Challenge) error {
|
||||
dnsName := challenge.DNS01TXTRecordName()
|
||||
|
||||
defer func() {
|
||||
// always forget about it so we don't leak memory
|
||||
s.txtRecordsMu.Lock()
|
||||
delete(s.txtRecords, dnsName)
|
||||
s.txtRecordsMu.Unlock()
|
||||
|
||||
// always do this last - but always do it!
|
||||
activeDNSChallenges.Unlock(dnsName)
|
||||
}()
|
||||
|
||||
// recall the record we created and zone we looked up
|
||||
s.txtRecordsMu.Lock()
|
||||
memory, ok := s.txtRecords[dnsName]
|
||||
if !ok {
|
||||
s.txtRecordsMu.Unlock()
|
||||
return fmt.Errorf("no memory of presenting a DNS record for %s (probably OK if presenting failed)", challenge.Identifier.Value)
|
||||
}
|
||||
s.txtRecordsMu.Unlock()
|
||||
|
||||
// clean up the record
|
||||
_, err := s.DNSProvider.DeleteRecords(ctx, memory.dnsZone, []libdns.Record{memory.rec})
|
||||
if err != nil {
|
||||
return fmt.Errorf("deleting temporary record for zone %s: %w", memory.dnsZone, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type dnsPresentMemory struct {
|
||||
dnsZone string
|
||||
rec libdns.Record
|
||||
}
|
||||
|
||||
// ACMEDNSProvider defines the set of operations required for
|
||||
// ACME challenges. A DNS provider must be able to append and
|
||||
// delete records in order to solve ACME challenges. Find one
|
||||
// you can use at https://github.com/libdns. If your provider
|
||||
// isn't implemented yet, feel free to contribute!
|
||||
type ACMEDNSProvider interface {
|
||||
libdns.RecordAppender
|
||||
libdns.RecordDeleter
|
||||
}
|
||||
|
||||
// activeDNSChallenges synchronizes DNS challenges for
|
||||
// names to ensure that challenges for the same ACME
|
||||
// DNS name do not overlap; for example, the TXT record
|
||||
// to make for both example.com and *.example.com are
|
||||
// the same; thus we cannot solve them concurrently.
|
||||
var activeDNSChallenges = newMapMutex()
|
||||
|
||||
// mapMutex implements named mutexes.
|
||||
type mapMutex struct {
|
||||
cond *sync.Cond
|
||||
set map[interface{}]struct{}
|
||||
}
|
||||
|
||||
func newMapMutex() *mapMutex {
|
||||
return &mapMutex{
|
||||
cond: sync.NewCond(new(sync.Mutex)),
|
||||
set: make(map[interface{}]struct{}),
|
||||
}
|
||||
}
|
||||
|
||||
func (mmu *mapMutex) Lock(key interface{}) {
|
||||
mmu.cond.L.Lock()
|
||||
defer mmu.cond.L.Unlock()
|
||||
for mmu.locked(key) {
|
||||
mmu.cond.Wait()
|
||||
}
|
||||
mmu.set[key] = struct{}{}
|
||||
return
|
||||
}
|
||||
|
||||
func (mmu *mapMutex) Unlock(key interface{}) {
|
||||
mmu.cond.L.Lock()
|
||||
defer mmu.cond.L.Unlock()
|
||||
delete(mmu.set, key)
|
||||
mmu.cond.Broadcast()
|
||||
}
|
||||
|
||||
func (mmu *mapMutex) locked(key interface{}) (ok bool) {
|
||||
_, ok = mmu.set[key]
|
||||
return
|
||||
}
|
||||
|
||||
// distributedSolver allows the ACME HTTP-01 and TLS-ALPN challenges
|
||||
// to be solved by an instance other than the one which initiated it.
|
||||
// This is useful behind load balancers or in other cluster/fleet
|
||||
@ -254,7 +467,7 @@ type distributedSolver struct {
|
||||
// Since the distributedSolver is only a
|
||||
// wrapper over an actual solver, place
|
||||
// the actual solver here.
|
||||
providerServer challenge.Provider
|
||||
solver acmez.Solver
|
||||
|
||||
// The CA endpoint URL associated with
|
||||
// this solver.
|
||||
@ -264,36 +477,32 @@ type distributedSolver struct {
|
||||
// Present invokes the underlying solver's Present method
|
||||
// and also stores domain, token, and keyAuth to the storage
|
||||
// backing the certificate cache of dhs.acmeManager.
|
||||
func (dhs distributedSolver) Present(domain, token, keyAuth string) error {
|
||||
infoBytes, err := json.Marshal(challengeInfo{
|
||||
Domain: domain,
|
||||
Token: token,
|
||||
KeyAuth: keyAuth,
|
||||
})
|
||||
func (dhs distributedSolver) Present(ctx context.Context, chal acme.Challenge) error {
|
||||
infoBytes, err := json.Marshal(chal)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = dhs.acmeManager.config.Storage.Store(dhs.challengeTokensKey(domain), infoBytes)
|
||||
err = dhs.acmeManager.config.Storage.Store(dhs.challengeTokensKey(chal.Identifier.Value), infoBytes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = dhs.providerServer.Present(domain, token, keyAuth)
|
||||
err = dhs.solver.Present(ctx, chal)
|
||||
if err != nil {
|
||||
return fmt.Errorf("presenting with embedded provider: %v", err)
|
||||
return fmt.Errorf("presenting with embedded solver: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// CleanUp invokes the underlying solver's CleanUp method
|
||||
// and also cleans up any assets saved to storage.
|
||||
func (dhs distributedSolver) CleanUp(domain, token, keyAuth string) error {
|
||||
err := dhs.acmeManager.config.Storage.Delete(dhs.challengeTokensKey(domain))
|
||||
func (dhs distributedSolver) CleanUp(ctx context.Context, chal acme.Challenge) error {
|
||||
err := dhs.acmeManager.config.Storage.Delete(dhs.challengeTokensKey(chal.Identifier.Value))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = dhs.providerServer.CleanUp(domain, token, keyAuth)
|
||||
err = dhs.solver.CleanUp(ctx, chal)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cleaning up embedded provider: %v", err)
|
||||
}
|
||||
@ -311,10 +520,6 @@ func (dhs distributedSolver) challengeTokensKey(domain string) string {
|
||||
return path.Join(dhs.challengeTokensPrefix(), StorageKeys.Safe(domain)+".json")
|
||||
}
|
||||
|
||||
type challengeInfo struct {
|
||||
Domain, Token, KeyAuth string
|
||||
}
|
||||
|
||||
// solverInfo associates a listener with the
|
||||
// number of challenges currently using it.
|
||||
type solverInfo struct {
|
||||
@ -410,3 +615,9 @@ var (
|
||||
solvers = make(map[string]*solverInfo)
|
||||
solversMu sync.Mutex
|
||||
)
|
||||
|
||||
// Interface guards
|
||||
var (
|
||||
_ acmez.Solver = (*DNS01Solver)(nil)
|
||||
_ acmez.Waiter = (*DNS01Solver)(nil)
|
||||
)
|
@ -15,6 +15,7 @@
|
||||
package certmagic
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
"path"
|
||||
"regexp"
|
||||
@ -86,8 +87,11 @@ type Locker interface {
|
||||
// To prevent deadlocks, all implementations (where this concern
|
||||
// is relevant) should put a reasonable expiration on the lock in
|
||||
// case Unlock is unable to be called due to some sort of network
|
||||
// failure or system crash.
|
||||
Lock(key string) error
|
||||
// failure or system crash. Additionally, implementations should
|
||||
// honor context cancellation as much as possible (in case the
|
||||
// caller wishes to give up and free resources before the lock
|
||||
// can be obtained).
|
||||
Lock(ctx context.Context, key string) error
|
||||
|
||||
// Unlock releases the lock for key. This method must ONLY be
|
||||
// called after a successful call to Lock, and only after the
|
||||
@ -223,8 +227,8 @@ func CleanUpOwnLocks() {
|
||||
}
|
||||
}
|
||||
|
||||
func obtainLock(storage Storage, lockKey string) error {
|
||||
err := storage.Lock(lockKey)
|
||||
func acquireLock(ctx context.Context, storage Storage, lockKey string) error {
|
||||
err := storage.Lock(ctx, lockKey)
|
||||
if err == nil {
|
||||
locksMu.Lock()
|
||||
locks[lockKey] = storage
|
81
vendor/github.com/go-acme/lego/v3/certificate/authorization.go
generated
vendored
81
vendor/github.com/go-acme/lego/v3/certificate/authorization.go
generated
vendored
@ -1,81 +0,0 @@
|
||||
package certificate
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/go-acme/lego/v3/acme"
|
||||
"github.com/go-acme/lego/v3/log"
|
||||
)
|
||||
|
||||
const (
|
||||
// overallRequestLimit is the overall number of request per second
|
||||
// limited on the "new-reg", "new-authz" and "new-cert" endpoints.
|
||||
// From the documentation the limitation is 20 requests per second,
|
||||
// but using 20 as value doesn't work but 18 do
|
||||
overallRequestLimit = 18
|
||||
)
|
||||
|
||||
func (c *Certifier) getAuthorizations(order acme.ExtendedOrder) ([]acme.Authorization, error) {
|
||||
resc, errc := make(chan acme.Authorization), make(chan domainError)
|
||||
|
||||
delay := time.Second / overallRequestLimit
|
||||
|
||||
for _, authzURL := range order.Authorizations {
|
||||
time.Sleep(delay)
|
||||
|
||||
go func(authzURL string) {
|
||||
authz, err := c.core.Authorizations.Get(authzURL)
|
||||
if err != nil {
|
||||
errc <- domainError{Domain: authz.Identifier.Value, Error: err}
|
||||
return
|
||||
}
|
||||
|
||||
resc <- authz
|
||||
}(authzURL)
|
||||
}
|
||||
|
||||
var responses []acme.Authorization
|
||||
failures := make(obtainError)
|
||||
for i := 0; i < len(order.Authorizations); i++ {
|
||||
select {
|
||||
case res := <-resc:
|
||||
responses = append(responses, res)
|
||||
case err := <-errc:
|
||||
failures[err.Domain] = err.Error
|
||||
}
|
||||
}
|
||||
|
||||
for i, auth := range order.Authorizations {
|
||||
log.Infof("[%s] AuthURL: %s", order.Identifiers[i].Value, auth)
|
||||
}
|
||||
|
||||
close(resc)
|
||||
close(errc)
|
||||
|
||||
// be careful to not return an empty failures map;
|
||||
// even if empty, they become non-nil error values
|
||||
if len(failures) > 0 {
|
||||
return responses, failures
|
||||
}
|
||||
return responses, nil
|
||||
}
|
||||
|
||||
func (c *Certifier) deactivateAuthorizations(order acme.ExtendedOrder) {
|
||||
for _, authzURL := range order.Authorizations {
|
||||
auth, err := c.core.Authorizations.Get(authzURL)
|
||||
if err != nil {
|
||||
log.Infof("Unable to get the authorization for: %s", authzURL)
|
||||
continue
|
||||
}
|
||||
|
||||
if auth.Status == acme.StatusValid {
|
||||
log.Infof("Skipping deactivating of valid auth: %s", authzURL)
|
||||
continue
|
||||
}
|
||||
|
||||
log.Infof("Deactivating auth: %s", authzURL)
|
||||
if c.core.Authorizations.Deactivate(authzURL) != nil {
|
||||
log.Infof("Unable to deactivate the authorization: %s", authzURL)
|
||||
}
|
||||
}
|
||||
}
|
522
vendor/github.com/go-acme/lego/v3/certificate/certificates.go
generated
vendored
522
vendor/github.com/go-acme/lego/v3/certificate/certificates.go
generated
vendored
@ -1,522 +0,0 @@
|
||||
package certificate
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto"
|
||||
"crypto/x509"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/go-acme/lego/v3/acme"
|
||||
"github.com/go-acme/lego/v3/acme/api"
|
||||
"github.com/go-acme/lego/v3/certcrypto"
|
||||
"github.com/go-acme/lego/v3/challenge"
|
||||
"github.com/go-acme/lego/v3/log"
|
||||
"github.com/go-acme/lego/v3/platform/wait"
|
||||
"golang.org/x/crypto/ocsp"
|
||||
"golang.org/x/net/idna"
|
||||
)
|
||||
|
||||
// maxBodySize is the maximum size of body that we will read.
|
||||
const maxBodySize = 1024 * 1024
|
||||
|
||||
// Resource represents a CA issued certificate.
|
||||
// PrivateKey, Certificate and IssuerCertificate are all
|
||||
// already PEM encoded and can be directly written to disk.
|
||||
// Certificate may be a certificate bundle,
|
||||
// depending on the options supplied to create it.
|
||||
type Resource struct {
|
||||
Domain string `json:"domain"`
|
||||
CertURL string `json:"certUrl"`
|
||||
CertStableURL string `json:"certStableUrl"`
|
||||
PrivateKey []byte `json:"-"`
|
||||
Certificate []byte `json:"-"`
|
||||
IssuerCertificate []byte `json:"-"`
|
||||
CSR []byte `json:"-"`
|
||||
}
|
||||
|
||||
// ObtainRequest The request to obtain certificate.
|
||||
//
|
||||
// The first domain in domains is used for the CommonName field of the certificate,
|
||||
// all other domains are added using the Subject Alternate Names extension.
|
||||
//
|
||||
// A new private key is generated for every invocation of the function Obtain.
|
||||
// If you do not want that you can supply your own private key in the privateKey parameter.
|
||||
// If this parameter is non-nil it will be used instead of generating a new one.
|
||||
//
|
||||
// If bundle is true, the []byte contains both the issuer certificate and your issued certificate as a bundle.
|
||||
type ObtainRequest struct {
|
||||
Domains []string
|
||||
Bundle bool
|
||||
PrivateKey crypto.PrivateKey
|
||||
MustStaple bool
|
||||
}
|
||||
|
||||
type resolver interface {
|
||||
Solve(authorizations []acme.Authorization) error
|
||||
}
|
||||
|
||||
type CertifierOptions struct {
|
||||
KeyType certcrypto.KeyType
|
||||
Timeout time.Duration
|
||||
}
|
||||
|
||||
// Certifier A service to obtain/renew/revoke certificates.
|
||||
type Certifier struct {
|
||||
core *api.Core
|
||||
resolver resolver
|
||||
options CertifierOptions
|
||||
}
|
||||
|
||||
// NewCertifier creates a Certifier.
|
||||
func NewCertifier(core *api.Core, resolver resolver, options CertifierOptions) *Certifier {
|
||||
return &Certifier{
|
||||
core: core,
|
||||
resolver: resolver,
|
||||
options: options,
|
||||
}
|
||||
}
|
||||
|
||||
// Obtain tries to obtain a single certificate using all domains passed into it.
|
||||
//
|
||||
// This function will never return a partial certificate.
|
||||
// If one domain in the list fails, the whole certificate will fail.
|
||||
func (c *Certifier) Obtain(request ObtainRequest) (*Resource, error) {
|
||||
if len(request.Domains) == 0 {
|
||||
return nil, errors.New("no domains to obtain a certificate for")
|
||||
}
|
||||
|
||||
domains := sanitizeDomain(request.Domains)
|
||||
|
||||
if request.Bundle {
|
||||
log.Infof("[%s] acme: Obtaining bundled SAN certificate", strings.Join(domains, ", "))
|
||||
} else {
|
||||
log.Infof("[%s] acme: Obtaining SAN certificate", strings.Join(domains, ", "))
|
||||
}
|
||||
|
||||
order, err := c.core.Orders.New(domains)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
authz, err := c.getAuthorizations(order)
|
||||
if err != nil {
|
||||
// If any challenge fails, return. Do not generate partial SAN certificates.
|
||||
c.deactivateAuthorizations(order)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = c.resolver.Solve(authz)
|
||||
if err != nil {
|
||||
// If any challenge fails, return. Do not generate partial SAN certificates.
|
||||
c.deactivateAuthorizations(order)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
log.Infof("[%s] acme: Validations succeeded; requesting certificates", strings.Join(domains, ", "))
|
||||
|
||||
failures := make(obtainError)
|
||||
cert, err := c.getForOrder(domains, order, request.Bundle, request.PrivateKey, request.MustStaple)
|
||||
if err != nil {
|
||||
for _, auth := range authz {
|
||||
failures[challenge.GetTargetedDomain(auth)] = err
|
||||
}
|
||||
}
|
||||
|
||||
// Do not return an empty failures map, because
|
||||
// it would still be a non-nil error value
|
||||
if len(failures) > 0 {
|
||||
return cert, failures
|
||||
}
|
||||
return cert, nil
|
||||
}
|
||||
|
||||
// ObtainForCSR tries to obtain a certificate matching the CSR passed into it.
|
||||
//
|
||||
// The domains are inferred from the CommonName and SubjectAltNames, if any.
|
||||
// The private key for this CSR is not required.
|
||||
//
|
||||
// If bundle is true, the []byte contains both the issuer certificate and your issued certificate as a bundle.
|
||||
//
|
||||
// This function will never return a partial certificate.
|
||||
// If one domain in the list fails, the whole certificate will fail.
|
||||
func (c *Certifier) ObtainForCSR(csr x509.CertificateRequest, bundle bool) (*Resource, error) {
|
||||
// figure out what domains it concerns
|
||||
// start with the common name
|
||||
domains := certcrypto.ExtractDomainsCSR(&csr)
|
||||
|
||||
if bundle {
|
||||
log.Infof("[%s] acme: Obtaining bundled SAN certificate given a CSR", strings.Join(domains, ", "))
|
||||
} else {
|
||||
log.Infof("[%s] acme: Obtaining SAN certificate given a CSR", strings.Join(domains, ", "))
|
||||
}
|
||||
|
||||
order, err := c.core.Orders.New(domains)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
authz, err := c.getAuthorizations(order)
|
||||
if err != nil {
|
||||
// If any challenge fails, return. Do not generate partial SAN certificates.
|
||||
c.deactivateAuthorizations(order)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = c.resolver.Solve(authz)
|
||||
if err != nil {
|
||||
// If any challenge fails, return. Do not generate partial SAN certificates.
|
||||
c.deactivateAuthorizations(order)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
log.Infof("[%s] acme: Validations succeeded; requesting certificates", strings.Join(domains, ", "))
|
||||
|
||||
failures := make(obtainError)
|
||||
cert, err := c.getForCSR(domains, order, bundle, csr.Raw, nil)
|
||||
if err != nil {
|
||||
for _, auth := range authz {
|
||||
failures[challenge.GetTargetedDomain(auth)] = err
|
||||
}
|
||||
}
|
||||
|
||||
if cert != nil {
|
||||
// Add the CSR to the certificate so that it can be used for renewals.
|
||||
cert.CSR = certcrypto.PEMEncode(&csr)
|
||||
}
|
||||
|
||||
// Do not return an empty failures map,
|
||||
// because it would still be a non-nil error value
|
||||
if len(failures) > 0 {
|
||||
return cert, failures
|
||||
}
|
||||
return cert, nil
|
||||
}
|
||||
|
||||
func (c *Certifier) getForOrder(domains []string, order acme.ExtendedOrder, bundle bool, privateKey crypto.PrivateKey, mustStaple bool) (*Resource, error) {
|
||||
if privateKey == nil {
|
||||
var err error
|
||||
privateKey, err = certcrypto.GeneratePrivateKey(c.options.KeyType)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// Determine certificate name(s) based on the authorization resources
|
||||
commonName := domains[0]
|
||||
|
||||
// RFC8555 Section 7.4 "Applying for Certificate Issuance"
|
||||
// https://tools.ietf.org/html/rfc8555#section-7.4
|
||||
// says:
|
||||
// Clients SHOULD NOT make any assumptions about the sort order of
|
||||
// "identifiers" or "authorizations" elements in the returned order
|
||||
// object.
|
||||
san := []string{commonName}
|
||||
for _, auth := range order.Identifiers {
|
||||
if auth.Value != commonName {
|
||||
san = append(san, auth.Value)
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: should the CSR be customizable?
|
||||
csr, err := certcrypto.GenerateCSR(privateKey, commonName, san, mustStaple)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return c.getForCSR(domains, order, bundle, csr, certcrypto.PEMEncode(privateKey))
|
||||
}
|
||||
|
||||
func (c *Certifier) getForCSR(domains []string, order acme.ExtendedOrder, bundle bool, csr []byte, privateKeyPem []byte) (*Resource, error) {
|
||||
respOrder, err := c.core.Orders.UpdateForCSR(order.Finalize, csr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
commonName := domains[0]
|
||||
certRes := &Resource{
|
||||
Domain: commonName,
|
||||
CertURL: respOrder.Certificate,
|
||||
PrivateKey: privateKeyPem,
|
||||
}
|
||||
|
||||
if respOrder.Status == acme.StatusValid {
|
||||
// if the certificate is available right away, short cut!
|
||||
ok, errR := c.checkResponse(respOrder, certRes, bundle)
|
||||
if errR != nil {
|
||||
return nil, errR
|
||||
}
|
||||
|
||||
if ok {
|
||||
return certRes, nil
|
||||
}
|
||||
}
|
||||
|
||||
timeout := c.options.Timeout
|
||||
if c.options.Timeout <= 0 {
|
||||
timeout = 30 * time.Second
|
||||
}
|
||||
|
||||
err = wait.For("certificate", timeout, timeout/60, func() (bool, error) {
|
||||
ord, errW := c.core.Orders.Get(order.Location)
|
||||
if errW != nil {
|
||||
return false, errW
|
||||
}
|
||||
|
||||
done, errW := c.checkResponse(ord, certRes, bundle)
|
||||
if errW != nil {
|
||||
return false, errW
|
||||
}
|
||||
|
||||
return done, nil
|
||||
})
|
||||
|
||||
return certRes, err
|
||||
}
|
||||
|
||||
// checkResponse checks to see if the certificate is ready and a link is contained in the response.
|
||||
//
|
||||
// If so, loads it into certRes and returns true.
|
||||
// If the cert is not yet ready, it returns false.
|
||||
//
|
||||
// The certRes input should already have the Domain (common name) field populated.
|
||||
//
|
||||
// If bundle is true, the certificate will be bundled with the issuer's cert.
|
||||
func (c *Certifier) checkResponse(order acme.Order, certRes *Resource, bundle bool) (bool, error) {
|
||||
valid, err := checkOrderStatus(order)
|
||||
if err != nil || !valid {
|
||||
return valid, err
|
||||
}
|
||||
|
||||
cert, issuer, err := c.core.Certificates.Get(order.Certificate, bundle)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
log.Infof("[%s] Server responded with a certificate.", certRes.Domain)
|
||||
|
||||
certRes.IssuerCertificate = issuer
|
||||
certRes.Certificate = cert
|
||||
certRes.CertURL = order.Certificate
|
||||
certRes.CertStableURL = order.Certificate
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// Revoke takes a PEM encoded certificate or bundle and tries to revoke it at the CA.
|
||||
func (c *Certifier) Revoke(cert []byte) error {
|
||||
certificates, err := certcrypto.ParsePEMBundle(cert)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
x509Cert := certificates[0]
|
||||
if x509Cert.IsCA {
|
||||
return errors.New("certificate bundle starts with a CA certificate")
|
||||
}
|
||||
|
||||
revokeMsg := acme.RevokeCertMessage{
|
||||
Certificate: base64.RawURLEncoding.EncodeToString(x509Cert.Raw),
|
||||
}
|
||||
|
||||
return c.core.Certificates.Revoke(revokeMsg)
|
||||
}
|
||||
|
||||
// Renew takes a Resource and tries to renew the certificate.
|
||||
//
|
||||
// If the renewal process succeeds, the new certificate will ge returned in a new CertResource.
|
||||
// Please be aware that this function will return a new certificate in ANY case that is not an error.
|
||||
// If the server does not provide us with a new cert on a GET request to the CertURL
|
||||
// this function will start a new-cert flow where a new certificate gets generated.
|
||||
//
|
||||
// If bundle is true, the []byte contains both the issuer certificate and your issued certificate as a bundle.
|
||||
//
|
||||
// For private key reuse the PrivateKey property of the passed in Resource should be non-nil.
|
||||
func (c *Certifier) Renew(certRes Resource, bundle, mustStaple bool) (*Resource, error) {
|
||||
// Input certificate is PEM encoded.
|
||||
// Decode it here as we may need the decoded cert later on in the renewal process.
|
||||
// The input may be a bundle or a single certificate.
|
||||
certificates, err := certcrypto.ParsePEMBundle(certRes.Certificate)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
x509Cert := certificates[0]
|
||||
if x509Cert.IsCA {
|
||||
return nil, fmt.Errorf("[%s] Certificate bundle starts with a CA certificate", certRes.Domain)
|
||||
}
|
||||
|
||||
// This is just meant to be informal for the user.
|
||||
timeLeft := x509Cert.NotAfter.Sub(time.Now().UTC())
|
||||
log.Infof("[%s] acme: Trying renewal with %d hours remaining", certRes.Domain, int(timeLeft.Hours()))
|
||||
|
||||
// We always need to request a new certificate to renew.
|
||||
// Start by checking to see if the certificate was based off a CSR,
|
||||
// and use that if it's defined.
|
||||
if len(certRes.CSR) > 0 {
|
||||
csr, errP := certcrypto.PemDecodeTox509CSR(certRes.CSR)
|
||||
if errP != nil {
|
||||
return nil, errP
|
||||
}
|
||||
|
||||
return c.ObtainForCSR(*csr, bundle)
|
||||
}
|
||||
|
||||
var privateKey crypto.PrivateKey
|
||||
if certRes.PrivateKey != nil {
|
||||
privateKey, err = certcrypto.ParsePEMPrivateKey(certRes.PrivateKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
query := ObtainRequest{
|
||||
Domains: certcrypto.ExtractDomains(x509Cert),
|
||||
Bundle: bundle,
|
||||
PrivateKey: privateKey,
|
||||
MustStaple: mustStaple,
|
||||
}
|
||||
return c.Obtain(query)
|
||||
}
|
||||
|
||||
// GetOCSP takes a PEM encoded cert or cert bundle returning the raw OCSP response,
|
||||
// the parsed response, and an error, if any.
|
||||
//
|
||||
// The returned []byte can be passed directly into the OCSPStaple property of a tls.Certificate.
|
||||
// If the bundle only contains the issued certificate,
|
||||
// this function will try to get the issuer certificate from the IssuingCertificateURL in the certificate.
|
||||
//
|
||||
// If the []byte and/or ocsp.Response return values are nil, the OCSP status may be assumed OCSPUnknown.
|
||||
func (c *Certifier) GetOCSP(bundle []byte) ([]byte, *ocsp.Response, error) {
|
||||
certificates, err := certcrypto.ParsePEMBundle(bundle)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// We expect the certificate slice to be ordered downwards the chain.
|
||||
// SRV CRT -> CA. We need to pull the leaf and issuer certs out of it,
|
||||
// which should always be the first two certificates.
|
||||
// If there's no OCSP server listed in the leaf cert, there's nothing to do.
|
||||
// And if we have only one certificate so far, we need to get the issuer cert.
|
||||
|
||||
issuedCert := certificates[0]
|
||||
|
||||
if len(issuedCert.OCSPServer) == 0 {
|
||||
return nil, nil, errors.New("no OCSP server specified in cert")
|
||||
}
|
||||
|
||||
if len(certificates) == 1 {
|
||||
// TODO: build fallback. If this fails, check the remaining array entries.
|
||||
if len(issuedCert.IssuingCertificateURL) == 0 {
|
||||
return nil, nil, errors.New("no issuing certificate URL")
|
||||
}
|
||||
|
||||
resp, errC := c.core.HTTPClient.Get(issuedCert.IssuingCertificateURL[0])
|
||||
if errC != nil {
|
||||
return nil, nil, errC
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
issuerBytes, errC := ioutil.ReadAll(http.MaxBytesReader(nil, resp.Body, maxBodySize))
|
||||
if errC != nil {
|
||||
return nil, nil, errC
|
||||
}
|
||||
|
||||
issuerCert, errC := x509.ParseCertificate(issuerBytes)
|
||||
if errC != nil {
|
||||
return nil, nil, errC
|
||||
}
|
||||
|
||||
// Insert it into the slice on position 0
|
||||
// We want it ordered right SRV CRT -> CA
|
||||
certificates = append(certificates, issuerCert)
|
||||
}
|
||||
|
||||
issuerCert := certificates[1]
|
||||
|
||||
// Finally kick off the OCSP request.
|
||||
ocspReq, err := ocsp.CreateRequest(issuedCert, issuerCert, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
resp, err := c.core.HTTPClient.Post(issuedCert.OCSPServer[0], "application/ocsp-request", bytes.NewReader(ocspReq))
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
ocspResBytes, err := ioutil.ReadAll(http.MaxBytesReader(nil, resp.Body, maxBodySize))
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
ocspRes, err := ocsp.ParseResponse(ocspResBytes, issuerCert)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return ocspResBytes, ocspRes, nil
|
||||
}
|
||||
|
||||
// Get attempts to fetch the certificate at the supplied URL.
|
||||
// The URL is the same as what would normally be supplied at the Resource's CertURL.
|
||||
//
|
||||
// The returned Resource will not have the PrivateKey and CSR fields populated as these will not be available.
|
||||
//
|
||||
// If bundle is true, the Certificate field in the returned Resource includes the issuer certificate.
|
||||
func (c *Certifier) Get(url string, bundle bool) (*Resource, error) {
|
||||
cert, issuer, err := c.core.Certificates.Get(url, bundle)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Parse the returned cert bundle so that we can grab the domain from the common name.
|
||||
x509Certs, err := certcrypto.ParsePEMBundle(cert)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &Resource{
|
||||
Domain: x509Certs[0].Subject.CommonName,
|
||||
Certificate: cert,
|
||||
IssuerCertificate: issuer,
|
||||
CertURL: url,
|
||||
CertStableURL: url,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func checkOrderStatus(order acme.Order) (bool, error) {
|
||||
switch order.Status {
|
||||
case acme.StatusValid:
|
||||
return true, nil
|
||||
case acme.StatusInvalid:
|
||||
return false, order.Error
|
||||
default:
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
|
||||
// https://tools.ietf.org/html/rfc8555#section-7.1.4
|
||||
// The domain name MUST be encoded
|
||||
// in the form in which it would appear in a certificate. That is, it
|
||||
// MUST be encoded according to the rules in Section 7 of [RFC5280].
|
||||
//
|
||||
// https://tools.ietf.org/html/rfc5280#section-7
|
||||
func sanitizeDomain(domains []string) []string {
|
||||
var sanitizedDomains []string
|
||||
for _, domain := range domains {
|
||||
sanitizedDomain, err := idna.ToASCII(domain)
|
||||
if err != nil {
|
||||
log.Infof("skip domain %q: unable to sanitize (punnycode): %v", domain, err)
|
||||
} else {
|
||||
sanitizedDomains = append(sanitizedDomains, sanitizedDomain)
|
||||
}
|
||||
}
|
||||
return sanitizedDomains
|
||||
}
|
30
vendor/github.com/go-acme/lego/v3/certificate/errors.go
generated
vendored
30
vendor/github.com/go-acme/lego/v3/certificate/errors.go
generated
vendored
@ -1,30 +0,0 @@
|
||||
package certificate
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"sort"
|
||||
)
|
||||
|
||||
// obtainError is returned when there are specific errors available per domain.
|
||||
type obtainError map[string]error
|
||||
|
||||
func (e obtainError) Error() string {
|
||||
buffer := bytes.NewBufferString("error: one or more domains had a problem:\n")
|
||||
|
||||
var domains []string
|
||||
for domain := range e {
|
||||
domains = append(domains, domain)
|
||||
}
|
||||
sort.Strings(domains)
|
||||
|
||||
for _, domain := range domains {
|
||||
buffer.WriteString(fmt.Sprintf("[%s] %s\n", domain, e[domain]))
|
||||
}
|
||||
return buffer.String()
|
||||
}
|
||||
|
||||
type domainError struct {
|
||||
Domain string
|
||||
Error error
|
||||
}
|
184
vendor/github.com/go-acme/lego/v3/challenge/http01/domain_matcher.go
generated
vendored
184
vendor/github.com/go-acme/lego/v3/challenge/http01/domain_matcher.go
generated
vendored
@ -1,184 +0,0 @@
|
||||
package http01
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// A domainMatcher tries to match a domain (the one we're requesting a certificate for)
|
||||
// in the HTTP request coming from the ACME validation servers.
|
||||
// This step is part of DNS rebind attack prevention,
|
||||
// where the webserver matches incoming requests to a list of domain the server acts authoritative for.
|
||||
//
|
||||
// The most simple check involves finding the domain in the HTTP Host header;
|
||||
// this is what hostMatcher does.
|
||||
// Use it, when the http01.ProviderServer is directly reachable from the internet,
|
||||
// or when it operates behind a transparent proxy.
|
||||
//
|
||||
// In many (reverse) proxy setups, Apache and NGINX traditionally move the Host header to a new header named X-Forwarded-Host.
|
||||
// Use arbitraryMatcher("X-Forwarded-Host") in this case,
|
||||
// or the appropriate header name for other proxy servers.
|
||||
//
|
||||
// RFC7239 has standardized the different forwarding headers into a single header named Forwarded.
|
||||
// The header value has a different format, so you should use forwardedMatcher
|
||||
// when the http01.ProviderServer operates behind a RFC7239 compatible proxy.
|
||||
// https://tools.ietf.org/html/rfc7239
|
||||
//
|
||||
// Note: RFC7239 also reminds us, "that an HTTP list [...] may be split over multiple header fields" (section 7.1),
|
||||
// meaning that
|
||||
// X-Header: a
|
||||
// X-Header: b
|
||||
// is equal to
|
||||
// X-Header: a, b
|
||||
//
|
||||
// All matcher implementations (explicitly not excluding arbitraryMatcher!)
|
||||
// have in common that they only match against the first value in such lists.
|
||||
type domainMatcher interface {
|
||||
// matches checks whether the request is valid for the given domain.
|
||||
matches(request *http.Request, domain string) bool
|
||||
|
||||
// name returns the header name used in the check.
|
||||
// This is primarily used to create meaningful error messages.
|
||||
name() string
|
||||
}
|
||||
|
||||
// hostMatcher checks whether (*net/http).Request.Host starts with a domain name.
|
||||
type hostMatcher struct{}
|
||||
|
||||
func (m *hostMatcher) name() string {
|
||||
return "Host"
|
||||
}
|
||||
|
||||
func (m *hostMatcher) matches(r *http.Request, domain string) bool {
|
||||
return strings.HasPrefix(r.Host, domain)
|
||||
}
|
||||
|
||||
// hostMatcher checks whether the specified (*net/http.Request).Header value starts with a domain name.
|
||||
type arbitraryMatcher string
|
||||
|
||||
func (m arbitraryMatcher) name() string {
|
||||
return string(m)
|
||||
}
|
||||
|
||||
func (m arbitraryMatcher) matches(r *http.Request, domain string) bool {
|
||||
return strings.HasPrefix(r.Header.Get(m.name()), domain)
|
||||
}
|
||||
|
||||
// forwardedMatcher checks whether the Forwarded header contains a "host" element starting with a domain name.
|
||||
// See https://tools.ietf.org/html/rfc7239 for details.
|
||||
type forwardedMatcher struct{}
|
||||
|
||||
func (m *forwardedMatcher) name() string {
|
||||
return "Forwarded"
|
||||
}
|
||||
|
||||
func (m *forwardedMatcher) matches(r *http.Request, domain string) bool {
|
||||
fwds, err := parseForwardedHeader(r.Header.Get(m.name()))
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if len(fwds) == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
host := fwds[0]["host"]
|
||||
return strings.HasPrefix(host, domain)
|
||||
}
|
||||
|
||||
// parsing requires some form of state machine.
|
||||
func parseForwardedHeader(s string) (elements []map[string]string, err error) {
|
||||
cur := make(map[string]string)
|
||||
key := ""
|
||||
val := ""
|
||||
inquote := false
|
||||
|
||||
pos := 0
|
||||
l := len(s)
|
||||
for i := 0; i < l; i++ {
|
||||
r := rune(s[i])
|
||||
|
||||
if inquote {
|
||||
if r == '"' {
|
||||
cur[key] = s[pos:i]
|
||||
key = ""
|
||||
pos = i
|
||||
inquote = false
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
switch {
|
||||
case r == '"': // start of quoted-string
|
||||
if key == "" {
|
||||
return nil, fmt.Errorf("unexpected quoted string as pos %d", i)
|
||||
}
|
||||
inquote = true
|
||||
pos = i + 1
|
||||
|
||||
case r == ';': // end of forwarded-pair
|
||||
cur[key] = s[pos:i]
|
||||
key = ""
|
||||
i = skipWS(s, i)
|
||||
pos = i + 1
|
||||
|
||||
case r == '=': // end of token
|
||||
key = strings.ToLower(strings.TrimFunc(s[pos:i], isWS))
|
||||
i = skipWS(s, i)
|
||||
pos = i + 1
|
||||
|
||||
case r == ',': // end of forwarded-element
|
||||
if key != "" {
|
||||
if val == "" {
|
||||
val = s[pos:i]
|
||||
}
|
||||
cur[key] = val
|
||||
}
|
||||
elements = append(elements, cur)
|
||||
cur = make(map[string]string)
|
||||
key = ""
|
||||
val = ""
|
||||
|
||||
i = skipWS(s, i)
|
||||
pos = i + 1
|
||||
case tchar(r) || isWS(r): // valid token character or whitespace
|
||||
continue
|
||||
default:
|
||||
return nil, fmt.Errorf("invalid token character at pos %d: %c", i, r)
|
||||
}
|
||||
}
|
||||
|
||||
if inquote {
|
||||
return nil, fmt.Errorf("unterminated quoted-string at pos %d", len(s))
|
||||
}
|
||||
|
||||
if key != "" {
|
||||
if pos < len(s) {
|
||||
val = s[pos:]
|
||||
}
|
||||
cur[key] = val
|
||||
}
|
||||
if len(cur) > 0 {
|
||||
elements = append(elements, cur)
|
||||
}
|
||||
return elements, nil
|
||||
}
|
||||
|
||||
func tchar(r rune) bool {
|
||||
return strings.ContainsRune("!#$%&'*+-.^_`|~", r) ||
|
||||
'0' <= r && r <= '9' ||
|
||||
'a' <= r && r <= 'z' ||
|
||||
'A' <= r && r <= 'Z'
|
||||
}
|
||||
|
||||
func skipWS(s string, i int) int {
|
||||
for isWS(rune(s[i+1])) {
|
||||
i++
|
||||
}
|
||||
return i
|
||||
}
|
||||
|
||||
func isWS(r rune) bool {
|
||||
return strings.ContainsRune(" \t\v\r\n", r)
|
||||
}
|
65
vendor/github.com/go-acme/lego/v3/challenge/http01/http_challenge.go
generated
vendored
65
vendor/github.com/go-acme/lego/v3/challenge/http01/http_challenge.go
generated
vendored
@ -1,65 +0,0 @@
|
||||
package http01
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/go-acme/lego/v3/acme"
|
||||
"github.com/go-acme/lego/v3/acme/api"
|
||||
"github.com/go-acme/lego/v3/challenge"
|
||||
"github.com/go-acme/lego/v3/log"
|
||||
)
|
||||
|
||||
type ValidateFunc func(core *api.Core, domain string, chlng acme.Challenge) error
|
||||
|
||||
// ChallengePath returns the URL path for the `http-01` challenge.
|
||||
func ChallengePath(token string) string {
|
||||
return "/.well-known/acme-challenge/" + token
|
||||
}
|
||||
|
||||
type Challenge struct {
|
||||
core *api.Core
|
||||
validate ValidateFunc
|
||||
provider challenge.Provider
|
||||
}
|
||||
|
||||
func NewChallenge(core *api.Core, validate ValidateFunc, provider challenge.Provider) *Challenge {
|
||||
return &Challenge{
|
||||
core: core,
|
||||
validate: validate,
|
||||
provider: provider,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Challenge) SetProvider(provider challenge.Provider) {
|
||||
c.provider = provider
|
||||
}
|
||||
|
||||
func (c *Challenge) Solve(authz acme.Authorization) error {
|
||||
domain := challenge.GetTargetedDomain(authz)
|
||||
log.Infof("[%s] acme: Trying to solve HTTP-01", domain)
|
||||
|
||||
chlng, err := challenge.FindChallenge(challenge.HTTP01, authz)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Generate the Key Authorization for the challenge
|
||||
keyAuth, err := c.core.GetKeyAuthorization(chlng.Token)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = c.provider.Present(authz.Identifier.Value, chlng.Token, keyAuth)
|
||||
if err != nil {
|
||||
return fmt.Errorf("[%s] acme: error presenting token: %w", domain, err)
|
||||
}
|
||||
defer func() {
|
||||
err := c.provider.CleanUp(authz.Identifier.Value, chlng.Token, keyAuth)
|
||||
if err != nil {
|
||||
log.Warnf("[%s] acme: cleaning up failed: %v", domain, err)
|
||||
}
|
||||
}()
|
||||
|
||||
chlng.KeyAuthorization = keyAuth
|
||||
return c.validate(c.core, domain, chlng)
|
||||
}
|
122
vendor/github.com/go-acme/lego/v3/challenge/http01/http_challenge_server.go
generated
vendored
122
vendor/github.com/go-acme/lego/v3/challenge/http01/http_challenge_server.go
generated
vendored
@ -1,122 +0,0 @@
|
||||
package http01
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/textproto"
|
||||
"strings"
|
||||
|
||||
"github.com/go-acme/lego/v3/log"
|
||||
)
|
||||
|
||||
// ProviderServer implements ChallengeProvider for `http-01` challenge.
|
||||
// It may be instantiated without using the NewProviderServer function if
|
||||
// you want only to use the default values.
|
||||
type ProviderServer struct {
|
||||
iface string
|
||||
port string
|
||||
matcher domainMatcher
|
||||
done chan bool
|
||||
listener net.Listener
|
||||
}
|
||||
|
||||
// NewProviderServer creates a new ProviderServer on the selected interface and port.
|
||||
// Setting iface and / or port to an empty string will make the server fall back to
|
||||
// the "any" interface and port 80 respectively.
|
||||
func NewProviderServer(iface, port string) *ProviderServer {
|
||||
if port == "" {
|
||||
port = "80"
|
||||
}
|
||||
|
||||
return &ProviderServer{iface: iface, port: port, matcher: &hostMatcher{}}
|
||||
}
|
||||
|
||||
// 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 {
|
||||
var err error
|
||||
s.listener, err = net.Listen("tcp", s.GetAddress())
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not start HTTP server for challenge: %w", err)
|
||||
}
|
||||
|
||||
s.done = make(chan bool)
|
||||
go s.serve(domain, token, keyAuth)
|
||||
return nil
|
||||
}
|
||||
|
||||
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 {
|
||||
if s.listener == nil {
|
||||
return nil
|
||||
}
|
||||
s.listener.Close()
|
||||
<-s.done
|
||||
return nil
|
||||
}
|
||||
|
||||
// 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 standardized 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)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *ProviderServer) serve(domain, token, keyAuth string) {
|
||||
path := ChallengePath(token)
|
||||
|
||||
// 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).
|
||||
mux := http.NewServeMux()
|
||||
mux.HandleFunc(path, func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method == http.MethodGet && s.matcher.matches(r, domain) {
|
||||
w.Header().Add("Content-Type", "text/plain")
|
||||
_, err := w.Write([]byte(keyAuth))
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
log.Infof("[%s] Served key authentication", domain)
|
||||
} else {
|
||||
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())
|
||||
_, err := w.Write([]byte("TEST"))
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
httpServer := &http.Server{Handler: mux}
|
||||
|
||||
// Once httpServer is shut down
|
||||
// we don't want any lingering connections, so disable KeepAlives.
|
||||
httpServer.SetKeepAlivesEnabled(false)
|
||||
|
||||
err := httpServer.Serve(s.listener)
|
||||
if err != nil && !strings.Contains(err.Error(), "use of closed network connection") {
|
||||
log.Println(err)
|
||||
}
|
||||
s.done <- true
|
||||
}
|
25
vendor/github.com/go-acme/lego/v3/challenge/resolver/errors.go
generated
vendored
25
vendor/github.com/go-acme/lego/v3/challenge/resolver/errors.go
generated
vendored
@ -1,25 +0,0 @@
|
||||
package resolver
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"sort"
|
||||
)
|
||||
|
||||
// obtainError is returned when there are specific errors available per domain.
|
||||
type obtainError map[string]error
|
||||
|
||||
func (e obtainError) Error() string {
|
||||
buffer := bytes.NewBufferString("error: one or more domains had a problem:\n")
|
||||
|
||||
var domains []string
|
||||
for domain := range e {
|
||||
domains = append(domains, domain)
|
||||
}
|
||||
sort.Strings(domains)
|
||||
|
||||
for _, domain := range domains {
|
||||
buffer.WriteString(fmt.Sprintf("[%s] %s\n", domain, e[domain]))
|
||||
}
|
||||
return buffer.String()
|
||||
}
|
173
vendor/github.com/go-acme/lego/v3/challenge/resolver/prober.go
generated
vendored
173
vendor/github.com/go-acme/lego/v3/challenge/resolver/prober.go
generated
vendored
@ -1,173 +0,0 @@
|
||||
package resolver
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/go-acme/lego/v3/acme"
|
||||
"github.com/go-acme/lego/v3/challenge"
|
||||
"github.com/go-acme/lego/v3/log"
|
||||
)
|
||||
|
||||
// Interface for all challenge solvers to implement.
|
||||
type solver interface {
|
||||
Solve(authorization acme.Authorization) error
|
||||
}
|
||||
|
||||
// Interface for challenges like dns, where we can set a record in advance for ALL challenges.
|
||||
// This saves quite a bit of time vs creating the records and solving them serially.
|
||||
type preSolver interface {
|
||||
PreSolve(authorization acme.Authorization) error
|
||||
}
|
||||
|
||||
// Interface for challenges like dns, where we can solve all the challenges before to delete them.
|
||||
type cleanup interface {
|
||||
CleanUp(authorization acme.Authorization) error
|
||||
}
|
||||
|
||||
type sequential interface {
|
||||
Sequential() (bool, time.Duration)
|
||||
}
|
||||
|
||||
// an authz with the solver we have chosen and the index of the challenge associated with it.
|
||||
type selectedAuthSolver struct {
|
||||
authz acme.Authorization
|
||||
solver solver
|
||||
}
|
||||
|
||||
type Prober struct {
|
||||
solverManager *SolverManager
|
||||
}
|
||||
|
||||
func NewProber(solverManager *SolverManager) *Prober {
|
||||
return &Prober{
|
||||
solverManager: solverManager,
|
||||
}
|
||||
}
|
||||
|
||||
// Solve Looks through the challenge combinations to find a solvable match.
|
||||
// Then solves the challenges in series and returns.
|
||||
func (p *Prober) Solve(authorizations []acme.Authorization) error {
|
||||
failures := make(obtainError)
|
||||
|
||||
var authSolvers []*selectedAuthSolver
|
||||
var authSolversSequential []*selectedAuthSolver
|
||||
|
||||
// Loop through the resources, basically through the domains.
|
||||
// First pass just selects a solver for each authz.
|
||||
for _, authz := range authorizations {
|
||||
domain := challenge.GetTargetedDomain(authz)
|
||||
if authz.Status == acme.StatusValid {
|
||||
// Boulder might recycle recent validated authz (see issue #267)
|
||||
log.Infof("[%s] acme: authorization already valid; skipping challenge", domain)
|
||||
continue
|
||||
}
|
||||
|
||||
if solvr := p.solverManager.chooseSolver(authz); solvr != nil {
|
||||
authSolver := &selectedAuthSolver{authz: authz, solver: solvr}
|
||||
|
||||
switch s := solvr.(type) {
|
||||
case sequential:
|
||||
if ok, _ := s.Sequential(); ok {
|
||||
authSolversSequential = append(authSolversSequential, authSolver)
|
||||
} else {
|
||||
authSolvers = append(authSolvers, authSolver)
|
||||
}
|
||||
default:
|
||||
authSolvers = append(authSolvers, authSolver)
|
||||
}
|
||||
} else {
|
||||
failures[domain] = fmt.Errorf("[%s] acme: could not determine solvers", domain)
|
||||
}
|
||||
}
|
||||
|
||||
parallelSolve(authSolvers, failures)
|
||||
|
||||
sequentialSolve(authSolversSequential, failures)
|
||||
|
||||
// Be careful not to return an empty failures map,
|
||||
// for even an empty obtainError is a non-nil error value
|
||||
if len(failures) > 0 {
|
||||
return failures
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func sequentialSolve(authSolvers []*selectedAuthSolver, failures obtainError) {
|
||||
for i, authSolver := range authSolvers {
|
||||
// Submit the challenge
|
||||
domain := challenge.GetTargetedDomain(authSolver.authz)
|
||||
|
||||
if solvr, ok := authSolver.solver.(preSolver); ok {
|
||||
err := solvr.PreSolve(authSolver.authz)
|
||||
if err != nil {
|
||||
failures[domain] = err
|
||||
cleanUp(authSolver.solver, authSolver.authz)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// Solve challenge
|
||||
err := authSolver.solver.Solve(authSolver.authz)
|
||||
if err != nil {
|
||||
failures[domain] = err
|
||||
cleanUp(authSolver.solver, authSolver.authz)
|
||||
continue
|
||||
}
|
||||
|
||||
// Clean challenge
|
||||
cleanUp(authSolver.solver, authSolver.authz)
|
||||
|
||||
if len(authSolvers)-1 > i {
|
||||
solvr := authSolver.solver.(sequential)
|
||||
_, interval := solvr.Sequential()
|
||||
log.Infof("sequence: wait for %s", interval)
|
||||
time.Sleep(interval)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func parallelSolve(authSolvers []*selectedAuthSolver, failures obtainError) {
|
||||
// For all valid preSolvers, first submit the challenges so they have max time to propagate
|
||||
for _, authSolver := range authSolvers {
|
||||
authz := authSolver.authz
|
||||
if solvr, ok := authSolver.solver.(preSolver); ok {
|
||||
err := solvr.PreSolve(authz)
|
||||
if err != nil {
|
||||
failures[challenge.GetTargetedDomain(authz)] = err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
defer func() {
|
||||
// Clean all created TXT records
|
||||
for _, authSolver := range authSolvers {
|
||||
cleanUp(authSolver.solver, authSolver.authz)
|
||||
}
|
||||
}()
|
||||
|
||||
// Finally solve all challenges for real
|
||||
for _, authSolver := range authSolvers {
|
||||
authz := authSolver.authz
|
||||
domain := challenge.GetTargetedDomain(authz)
|
||||
if failures[domain] != nil {
|
||||
// already failed in previous loop
|
||||
continue
|
||||
}
|
||||
|
||||
err := authSolver.solver.Solve(authz)
|
||||
if err != nil {
|
||||
failures[domain] = err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func cleanUp(solvr solver, authz acme.Authorization) {
|
||||
if solvr, ok := solvr.(cleanup); ok {
|
||||
domain := challenge.GetTargetedDomain(authz)
|
||||
err := solvr.CleanUp(authz)
|
||||
if err != nil {
|
||||
log.Warnf("[%s] acme: cleaning up failed: %v ", domain, err)
|
||||
}
|
||||
}
|
||||
}
|
169
vendor/github.com/go-acme/lego/v3/challenge/resolver/solver_manager.go
generated
vendored
169
vendor/github.com/go-acme/lego/v3/challenge/resolver/solver_manager.go
generated
vendored
@ -1,169 +0,0 @@
|
||||
package resolver
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/cenkalti/backoff/v4"
|
||||
"github.com/go-acme/lego/v3/acme"
|
||||
"github.com/go-acme/lego/v3/acme/api"
|
||||
"github.com/go-acme/lego/v3/challenge"
|
||||
"github.com/go-acme/lego/v3/challenge/dns01"
|
||||
"github.com/go-acme/lego/v3/challenge/http01"
|
||||
"github.com/go-acme/lego/v3/challenge/tlsalpn01"
|
||||
"github.com/go-acme/lego/v3/log"
|
||||
)
|
||||
|
||||
type byType []acme.Challenge
|
||||
|
||||
func (a byType) Len() int { return len(a) }
|
||||
func (a byType) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
||||
func (a byType) Less(i, j int) bool { return a[i].Type > a[j].Type }
|
||||
|
||||
type SolverManager struct {
|
||||
core *api.Core
|
||||
solvers map[challenge.Type]solver
|
||||
}
|
||||
|
||||
func NewSolversManager(core *api.Core) *SolverManager {
|
||||
return &SolverManager{
|
||||
solvers: map[challenge.Type]solver{},
|
||||
core: core,
|
||||
}
|
||||
}
|
||||
|
||||
// SetHTTP01Provider specifies a custom provider p that can solve the given HTTP-01 challenge.
|
||||
func (c *SolverManager) SetHTTP01Provider(p challenge.Provider) error {
|
||||
c.solvers[challenge.HTTP01] = http01.NewChallenge(c.core, validate, p)
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetTLSALPN01Provider specifies a custom provider p that can solve the given TLS-ALPN-01 challenge.
|
||||
func (c *SolverManager) SetTLSALPN01Provider(p challenge.Provider) error {
|
||||
c.solvers[challenge.TLSALPN01] = tlsalpn01.NewChallenge(c.core, validate, p)
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetDNS01Provider specifies a custom provider p that can solve the given DNS-01 challenge.
|
||||
func (c *SolverManager) SetDNS01Provider(p challenge.Provider, opts ...dns01.ChallengeOption) error {
|
||||
c.solvers[challenge.DNS01] = dns01.NewChallenge(c.core, validate, p, opts...)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Remove Remove a challenge type from the available solvers.
|
||||
func (c *SolverManager) Remove(chlgType challenge.Type) {
|
||||
delete(c.solvers, chlgType)
|
||||
}
|
||||
|
||||
// Checks all challenges from the server in order and returns the first matching solver.
|
||||
func (c *SolverManager) chooseSolver(authz acme.Authorization) solver {
|
||||
// Allow to have a deterministic challenge order
|
||||
sort.Sort(byType(authz.Challenges))
|
||||
|
||||
domain := challenge.GetTargetedDomain(authz)
|
||||
for _, chlg := range authz.Challenges {
|
||||
if solvr, ok := c.solvers[challenge.Type(chlg.Type)]; ok {
|
||||
log.Infof("[%s] acme: use %s solver", domain, chlg.Type)
|
||||
return solvr
|
||||
}
|
||||
log.Infof("[%s] acme: Could not find solver for: %s", domain, chlg.Type)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func validate(core *api.Core, domain string, chlg acme.Challenge) error {
|
||||
chlng, err := core.Challenges.New(chlg.URL)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to initiate challenge: %w", err)
|
||||
}
|
||||
|
||||
valid, err := checkChallengeStatus(chlng)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if valid {
|
||||
log.Infof("[%s] The server validated our request", domain)
|
||||
return nil
|
||||
}
|
||||
|
||||
ra, err := strconv.Atoi(chlng.RetryAfter)
|
||||
if err != nil {
|
||||
// The ACME server MUST return a Retry-After.
|
||||
// If it doesn't, we'll just poll hard.
|
||||
// Boulder does not implement the ability to retry challenges or the Retry-After header.
|
||||
// https://github.com/letsencrypt/boulder/blob/master/docs/acme-divergences.md#section-82
|
||||
ra = 5
|
||||
}
|
||||
initialInterval := time.Duration(ra) * time.Second
|
||||
|
||||
bo := backoff.NewExponentialBackOff()
|
||||
bo.InitialInterval = initialInterval
|
||||
bo.MaxInterval = 10 * initialInterval
|
||||
bo.MaxElapsedTime = 100 * initialInterval
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
|
||||
// After the path is sent, the ACME server will access our server.
|
||||
// Repeatedly check the server for an updated status on our request.
|
||||
operation := func() error {
|
||||
authz, err := core.Authorizations.Get(chlng.AuthorizationURL)
|
||||
if err != nil {
|
||||
cancel()
|
||||
return err
|
||||
}
|
||||
|
||||
valid, err := checkAuthorizationStatus(authz)
|
||||
if err != nil {
|
||||
cancel()
|
||||
return err
|
||||
}
|
||||
|
||||
if valid {
|
||||
log.Infof("[%s] The server validated our request", domain)
|
||||
return nil
|
||||
}
|
||||
|
||||
return errors.New("the server didn't respond to our request")
|
||||
}
|
||||
|
||||
return backoff.Retry(operation, backoff.WithContext(bo, ctx))
|
||||
}
|
||||
|
||||
func checkChallengeStatus(chlng acme.ExtendedChallenge) (bool, error) {
|
||||
switch chlng.Status {
|
||||
case acme.StatusValid:
|
||||
return true, nil
|
||||
case acme.StatusPending, acme.StatusProcessing:
|
||||
return false, nil
|
||||
case acme.StatusInvalid:
|
||||
return false, chlng.Error
|
||||
default:
|
||||
return false, errors.New("the server returned an unexpected state")
|
||||
}
|
||||
}
|
||||
|
||||
func checkAuthorizationStatus(authz acme.Authorization) (bool, error) {
|
||||
switch authz.Status {
|
||||
case acme.StatusValid:
|
||||
return true, nil
|
||||
case acme.StatusPending, acme.StatusProcessing:
|
||||
return false, nil
|
||||
case acme.StatusDeactivated, acme.StatusExpired, acme.StatusRevoked:
|
||||
return false, fmt.Errorf("the authorization state %s", authz.Status)
|
||||
case acme.StatusInvalid:
|
||||
for _, chlg := range authz.Challenges {
|
||||
if chlg.Status == acme.StatusInvalid && chlg.Error != nil {
|
||||
return false, chlg.Error
|
||||
}
|
||||
}
|
||||
return false, fmt.Errorf("the authorization state %s", authz.Status)
|
||||
default:
|
||||
return false, errors.New("the server returned an unexpected state")
|
||||
}
|
||||
}
|
129
vendor/github.com/go-acme/lego/v3/challenge/tlsalpn01/tls_alpn_challenge.go
generated
vendored
129
vendor/github.com/go-acme/lego/v3/challenge/tlsalpn01/tls_alpn_challenge.go
generated
vendored
@ -1,129 +0,0 @@
|
||||
package tlsalpn01
|
||||
|
||||
import (
|
||||
"crypto/rsa"
|
||||
"crypto/sha256"
|
||||
"crypto/tls"
|
||||
"crypto/x509/pkix"
|
||||
"encoding/asn1"
|
||||
"fmt"
|
||||
|
||||
"github.com/go-acme/lego/v3/acme"
|
||||
"github.com/go-acme/lego/v3/acme/api"
|
||||
"github.com/go-acme/lego/v3/certcrypto"
|
||||
"github.com/go-acme/lego/v3/challenge"
|
||||
"github.com/go-acme/lego/v3/log"
|
||||
)
|
||||
|
||||
// idPeAcmeIdentifierV1 is the SMI Security for PKIX Certification Extension OID referencing the ACME extension.
|
||||
// Reference: https://tools.ietf.org/html/draft-ietf-acme-tls-alpn-07#section-6.1
|
||||
var idPeAcmeIdentifierV1 = asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 1, 31}
|
||||
|
||||
type ValidateFunc func(core *api.Core, domain string, chlng acme.Challenge) error
|
||||
|
||||
type Challenge struct {
|
||||
core *api.Core
|
||||
validate ValidateFunc
|
||||
provider challenge.Provider
|
||||
}
|
||||
|
||||
func NewChallenge(core *api.Core, validate ValidateFunc, provider challenge.Provider) *Challenge {
|
||||
return &Challenge{
|
||||
core: core,
|
||||
validate: validate,
|
||||
provider: provider,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Challenge) SetProvider(provider challenge.Provider) {
|
||||
c.provider = provider
|
||||
}
|
||||
|
||||
// Solve manages the provider to validate and solve the challenge.
|
||||
func (c *Challenge) Solve(authz acme.Authorization) error {
|
||||
domain := authz.Identifier.Value
|
||||
log.Infof("[%s] acme: Trying to solve TLS-ALPN-01", challenge.GetTargetedDomain(authz))
|
||||
|
||||
chlng, err := challenge.FindChallenge(challenge.TLSALPN01, authz)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Generate the Key Authorization for the challenge
|
||||
keyAuth, err := c.core.GetKeyAuthorization(chlng.Token)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = c.provider.Present(domain, chlng.Token, keyAuth)
|
||||
if err != nil {
|
||||
return fmt.Errorf("[%s] acme: error presenting token: %w", challenge.GetTargetedDomain(authz), err)
|
||||
}
|
||||
defer func() {
|
||||
err := c.provider.CleanUp(domain, chlng.Token, keyAuth)
|
||||
if err != nil {
|
||||
log.Warnf("[%s] acme: cleaning up failed: %v", challenge.GetTargetedDomain(authz), err)
|
||||
}
|
||||
}()
|
||||
|
||||
chlng.KeyAuthorization = keyAuth
|
||||
return c.validate(c.core, domain, chlng)
|
||||
}
|
||||
|
||||
// ChallengeBlocks returns PEM blocks (certPEMBlock, keyPEMBlock) with the acmeValidation-v1 extension
|
||||
// and domain name for the `tls-alpn-01` challenge.
|
||||
func ChallengeBlocks(domain, keyAuth string) ([]byte, []byte, error) {
|
||||
// Compute the SHA-256 digest of the key authorization.
|
||||
zBytes := sha256.Sum256([]byte(keyAuth))
|
||||
|
||||
value, err := asn1.Marshal(zBytes[:sha256.Size])
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// Add the keyAuth digest as the acmeValidation-v1 extension
|
||||
// (marked as critical such that it won't be used by non-ACME software).
|
||||
// Reference: https://tools.ietf.org/html/draft-ietf-acme-tls-alpn-07#section-3
|
||||
extensions := []pkix.Extension{
|
||||
{
|
||||
Id: idPeAcmeIdentifierV1,
|
||||
Critical: true,
|
||||
Value: value,
|
||||
},
|
||||
}
|
||||
|
||||
// Generate a new RSA key for the certificates.
|
||||
tempPrivateKey, err := certcrypto.GeneratePrivateKey(certcrypto.RSA2048)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
rsaPrivateKey := tempPrivateKey.(*rsa.PrivateKey)
|
||||
|
||||
// Generate the PEM certificate using the provided private key, domain, and extra extensions.
|
||||
tempCertPEM, err := certcrypto.GeneratePemCert(rsaPrivateKey, domain, extensions)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// Encode the private key into a PEM format. We'll need to use it to generate the x509 keypair.
|
||||
rsaPrivatePEM := certcrypto.PEMEncode(rsaPrivateKey)
|
||||
|
||||
return tempCertPEM, rsaPrivatePEM, nil
|
||||
}
|
||||
|
||||
// ChallengeCert returns a certificate with the acmeValidation-v1 extension
|
||||
// and domain name for the `tls-alpn-01` challenge.
|
||||
func ChallengeCert(domain, keyAuth string) (*tls.Certificate, error) {
|
||||
tempCertPEM, rsaPrivatePEM, err := ChallengeBlocks(domain, keyAuth)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cert, err := tls.X509KeyPair(tempCertPEM, rsaPrivatePEM)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &cert, nil
|
||||
}
|
95
vendor/github.com/go-acme/lego/v3/challenge/tlsalpn01/tls_alpn_challenge_server.go
generated
vendored
95
vendor/github.com/go-acme/lego/v3/challenge/tlsalpn01/tls_alpn_challenge_server.go
generated
vendored
@ -1,95 +0,0 @@
|
||||
package tlsalpn01
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/go-acme/lego/v3/log"
|
||||
)
|
||||
|
||||
const (
|
||||
// ACMETLS1Protocol is the ALPN Protocol ID for the ACME-TLS/1 Protocol.
|
||||
ACMETLS1Protocol = "acme-tls/1"
|
||||
|
||||
// defaultTLSPort is the port that the ProviderServer will default to
|
||||
// when no other port is provided.
|
||||
defaultTLSPort = "443"
|
||||
)
|
||||
|
||||
// ProviderServer implements ChallengeProvider for `TLS-ALPN-01` challenge.
|
||||
// It may be instantiated without using the NewProviderServer
|
||||
// if you want only to use the default values.
|
||||
type ProviderServer struct {
|
||||
iface string
|
||||
port string
|
||||
listener net.Listener
|
||||
}
|
||||
|
||||
// NewProviderServer creates a new ProviderServer on the selected interface and port.
|
||||
// Setting iface and / or port to an empty string will make the server fall back to
|
||||
// the "any" interface and port 443 respectively.
|
||||
func NewProviderServer(iface, port string) *ProviderServer {
|
||||
return &ProviderServer{iface: iface, port: port}
|
||||
}
|
||||
|
||||
func (s *ProviderServer) GetAddress() string {
|
||||
return net.JoinHostPort(s.iface, s.port)
|
||||
}
|
||||
|
||||
// Present generates a certificate with a SHA-256 digest of the keyAuth provided
|
||||
// as the acmeValidation-v1 extension value to conform to the ACME-TLS-ALPN spec.
|
||||
func (s *ProviderServer) Present(domain, token, keyAuth string) error {
|
||||
if s.port == "" {
|
||||
// Fallback to port 443 if the port was not provided.
|
||||
s.port = defaultTLSPort
|
||||
}
|
||||
|
||||
// Generate the challenge certificate using the provided keyAuth and domain.
|
||||
cert, err := ChallengeCert(domain, keyAuth)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Place the generated certificate with the extension into the TLS config
|
||||
// so that it can serve the correct details.
|
||||
tlsConf := new(tls.Config)
|
||||
tlsConf.Certificates = []tls.Certificate{*cert}
|
||||
|
||||
// We must set that the `acme-tls/1` application level protocol is supported
|
||||
// so that the protocol negotiation can succeed. Reference:
|
||||
// https://tools.ietf.org/html/draft-ietf-acme-tls-alpn-07#section-6.2
|
||||
tlsConf.NextProtos = []string{ACMETLS1Protocol}
|
||||
|
||||
// Create the listener with the created tls.Config.
|
||||
s.listener, err = tls.Listen("tcp", s.GetAddress(), tlsConf)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not start HTTPS server for challenge: %w", err)
|
||||
}
|
||||
|
||||
// Shut the server down when we're finished.
|
||||
go func() {
|
||||
err := http.Serve(s.listener, nil)
|
||||
if err != nil && !strings.Contains(err.Error(), "use of closed network connection") {
|
||||
log.Println(err)
|
||||
}
|
||||
}()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CleanUp closes the HTTPS server.
|
||||
func (s *ProviderServer) CleanUp(domain, token, keyAuth string) error {
|
||||
if s.listener == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Server was created, close it.
|
||||
if err := s.listener.Close(); err != nil && err != http.ErrServerClosed {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
74
vendor/github.com/go-acme/lego/v3/lego/client.go
generated
vendored
74
vendor/github.com/go-acme/lego/v3/lego/client.go
generated
vendored
@ -1,74 +0,0 @@
|
||||
package lego
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/url"
|
||||
|
||||
"github.com/go-acme/lego/v3/acme/api"
|
||||
"github.com/go-acme/lego/v3/certificate"
|
||||
"github.com/go-acme/lego/v3/challenge/resolver"
|
||||
"github.com/go-acme/lego/v3/registration"
|
||||
)
|
||||
|
||||
// Client is the user-friendly way to ACME.
|
||||
type Client struct {
|
||||
Certificate *certificate.Certifier
|
||||
Challenge *resolver.SolverManager
|
||||
Registration *registration.Registrar
|
||||
core *api.Core
|
||||
}
|
||||
|
||||
// NewClient creates a new ACME client on behalf of the user.
|
||||
// The client will depend on the ACME directory located at CADirURL for the rest of its actions.
|
||||
// A private key of type keyType (see KeyType constants) will be generated when requesting a new certificate if one isn't provided.
|
||||
func NewClient(config *Config) (*Client, error) {
|
||||
if config == nil {
|
||||
return nil, errors.New("a configuration must be provided")
|
||||
}
|
||||
|
||||
_, err := url.Parse(config.CADirURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if config.HTTPClient == nil {
|
||||
return nil, errors.New("the HTTP client cannot be nil")
|
||||
}
|
||||
|
||||
privateKey := config.User.GetPrivateKey()
|
||||
if privateKey == nil {
|
||||
return nil, errors.New("private key was nil")
|
||||
}
|
||||
|
||||
var kid string
|
||||
if reg := config.User.GetRegistration(); reg != nil {
|
||||
kid = reg.URI
|
||||
}
|
||||
|
||||
core, err := api.New(config.HTTPClient, config.UserAgent, config.CADirURL, kid, privateKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
solversManager := resolver.NewSolversManager(core)
|
||||
|
||||
prober := resolver.NewProber(solversManager)
|
||||
certifier := certificate.NewCertifier(core, prober, certificate.CertifierOptions{KeyType: config.Certificate.KeyType, Timeout: config.Certificate.Timeout})
|
||||
|
||||
return &Client{
|
||||
Certificate: certifier,
|
||||
Challenge: solversManager,
|
||||
Registration: registration.NewRegistrar(core, config.User),
|
||||
core: core,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GetToSURL returns the current ToS URL from the Directory.
|
||||
func (c *Client) GetToSURL() string {
|
||||
return c.core.GetDirectory().Meta.TermsOfService
|
||||
}
|
||||
|
||||
// GetExternalAccountRequired returns the External Account Binding requirement of the Directory.
|
||||
func (c *Client) GetExternalAccountRequired() bool {
|
||||
return c.core.GetDirectory().Meta.ExternalAccountRequired
|
||||
}
|
104
vendor/github.com/go-acme/lego/v3/lego/client_config.go
generated
vendored
104
vendor/github.com/go-acme/lego/v3/lego/client_config.go
generated
vendored
@ -1,104 +0,0 @@
|
||||
package lego
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/go-acme/lego/v3/certcrypto"
|
||||
"github.com/go-acme/lego/v3/registration"
|
||||
)
|
||||
|
||||
const (
|
||||
// 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"
|
||||
|
||||
// LEDirectoryProduction URL to the Let's Encrypt production
|
||||
LEDirectoryProduction = "https://acme-v02.api.letsencrypt.org/directory"
|
||||
|
||||
// LEDirectoryStaging URL to the Let's Encrypt staging
|
||||
LEDirectoryStaging = "https://acme-staging-v02.api.letsencrypt.org/directory"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
CADirURL string
|
||||
User registration.User
|
||||
UserAgent string
|
||||
HTTPClient *http.Client
|
||||
Certificate CertificateConfig
|
||||
}
|
||||
|
||||
func NewConfig(user registration.User) *Config {
|
||||
return &Config{
|
||||
CADirURL: LEDirectoryProduction,
|
||||
User: user,
|
||||
HTTPClient: createDefaultHTTPClient(),
|
||||
Certificate: CertificateConfig{
|
||||
KeyType: certcrypto.RSA2048,
|
||||
Timeout: 30 * time.Second,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
type CertificateConfig struct {
|
||||
KeyType certcrypto.KeyType
|
||||
Timeout time.Duration
|
||||
}
|
||||
|
||||
// createDefaultHTTPClient Creates an HTTP client with a reasonable timeout value
|
||||
// and potentially a custom *x509.CertPool
|
||||
// based on the caCertificatesEnvVar environment variable (see the `initCertPool` function).
|
||||
func createDefaultHTTPClient() *http.Client {
|
||||
return &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(),
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
170
vendor/github.com/go-acme/lego/v3/registration/registar.go
generated
vendored
170
vendor/github.com/go-acme/lego/v3/registration/registar.go
generated
vendored
@ -1,170 +0,0 @@
|
||||
package registration
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
|
||||
"github.com/go-acme/lego/v3/acme"
|
||||
"github.com/go-acme/lego/v3/acme/api"
|
||||
"github.com/go-acme/lego/v3/log"
|
||||
)
|
||||
|
||||
// Resource represents all important information about a registration
|
||||
// of which the client needs to keep track itself.
|
||||
// WARNING: will be remove in the future (acme.ExtendedAccount), https://github.com/go-acme/lego/issues/855.
|
||||
type Resource struct {
|
||||
Body acme.Account `json:"body,omitempty"`
|
||||
URI string `json:"uri,omitempty"`
|
||||
}
|
||||
|
||||
type RegisterOptions struct {
|
||||
TermsOfServiceAgreed bool
|
||||
}
|
||||
|
||||
type RegisterEABOptions struct {
|
||||
TermsOfServiceAgreed bool
|
||||
Kid string
|
||||
HmacEncoded string
|
||||
}
|
||||
|
||||
type Registrar struct {
|
||||
core *api.Core
|
||||
user User
|
||||
}
|
||||
|
||||
func NewRegistrar(core *api.Core, user User) *Registrar {
|
||||
return &Registrar{
|
||||
core: core,
|
||||
user: user,
|
||||
}
|
||||
}
|
||||
|
||||
// Register the current account to the ACME server.
|
||||
func (r *Registrar) Register(options RegisterOptions) (*Resource, error) {
|
||||
if r == nil || r.user == nil {
|
||||
return nil, errors.New("acme: cannot register a nil client or user")
|
||||
}
|
||||
|
||||
accMsg := acme.Account{
|
||||
TermsOfServiceAgreed: options.TermsOfServiceAgreed,
|
||||
Contact: []string{},
|
||||
}
|
||||
|
||||
if r.user.GetEmail() != "" {
|
||||
log.Infof("acme: Registering account for %s", r.user.GetEmail())
|
||||
accMsg.Contact = []string{"mailto:" + r.user.GetEmail()}
|
||||
}
|
||||
|
||||
account, err := r.core.Accounts.New(accMsg)
|
||||
if err != nil {
|
||||
// FIXME seems impossible
|
||||
errorDetails, ok := err.(acme.ProblemDetails)
|
||||
if !ok || errorDetails.HTTPStatus != http.StatusConflict {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return &Resource{URI: account.Location, Body: account.Account}, nil
|
||||
}
|
||||
|
||||
// RegisterWithExternalAccountBinding Register the current account to the ACME server.
|
||||
func (r *Registrar) RegisterWithExternalAccountBinding(options RegisterEABOptions) (*Resource, error) {
|
||||
accMsg := acme.Account{
|
||||
TermsOfServiceAgreed: options.TermsOfServiceAgreed,
|
||||
Contact: []string{},
|
||||
}
|
||||
|
||||
if r.user.GetEmail() != "" {
|
||||
log.Infof("acme: Registering account for %s", r.user.GetEmail())
|
||||
accMsg.Contact = []string{"mailto:" + r.user.GetEmail()}
|
||||
}
|
||||
|
||||
account, err := r.core.Accounts.NewEAB(accMsg, options.Kid, options.HmacEncoded)
|
||||
if err != nil {
|
||||
errorDetails, ok := err.(acme.ProblemDetails)
|
||||
// FIXME seems impossible
|
||||
if !ok || errorDetails.HTTPStatus != http.StatusConflict {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return &Resource{URI: account.Location, Body: account.Account}, nil
|
||||
}
|
||||
|
||||
// QueryRegistration runs a POST request on the client's registration and returns the result.
|
||||
//
|
||||
// This is similar to the Register function,
|
||||
// but acting on an existing registration link and resource.
|
||||
func (r *Registrar) QueryRegistration() (*Resource, error) {
|
||||
if r == nil || r.user == nil {
|
||||
return nil, errors.New("acme: cannot query the registration of a nil client or user")
|
||||
}
|
||||
|
||||
// Log the URL here instead of the email as the email may not be set
|
||||
log.Infof("acme: Querying account for %s", r.user.GetRegistration().URI)
|
||||
|
||||
account, err := r.core.Accounts.Get(r.user.GetRegistration().URI)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &Resource{
|
||||
Body: account,
|
||||
// Location: header is not returned so this needs to be populated off of existing URI
|
||||
URI: r.user.GetRegistration().URI,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// UpdateRegistration update the user registration on the ACME server.
|
||||
func (r *Registrar) UpdateRegistration(options RegisterOptions) (*Resource, error) {
|
||||
if r == nil || r.user == nil {
|
||||
return nil, errors.New("acme: cannot update a nil client or user")
|
||||
}
|
||||
|
||||
accMsg := acme.Account{
|
||||
TermsOfServiceAgreed: options.TermsOfServiceAgreed,
|
||||
Contact: []string{},
|
||||
}
|
||||
|
||||
if r.user.GetEmail() != "" {
|
||||
log.Infof("acme: Registering account for %s", r.user.GetEmail())
|
||||
accMsg.Contact = []string{"mailto:" + r.user.GetEmail()}
|
||||
}
|
||||
|
||||
account, err := r.core.Accounts.Update(r.user.GetRegistration().URI, accMsg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &Resource{URI: account.Location, Body: account.Account}, nil
|
||||
}
|
||||
|
||||
// DeleteRegistration deletes the client's user registration from the ACME server.
|
||||
func (r *Registrar) DeleteRegistration() error {
|
||||
if r == nil || r.user == nil {
|
||||
return errors.New("acme: cannot unregister a nil client or user")
|
||||
}
|
||||
|
||||
log.Infof("acme: Deleting account for %s", r.user.GetEmail())
|
||||
|
||||
return r.core.Accounts.Deactivate(r.user.GetRegistration().URI)
|
||||
}
|
||||
|
||||
// ResolveAccountByKey will attempt to look up an account using the given account key
|
||||
// and return its registration resource.
|
||||
func (r *Registrar) ResolveAccountByKey() (*Resource, error) {
|
||||
log.Infof("acme: Trying to resolve account by key")
|
||||
|
||||
accMsg := acme.Account{OnlyReturnExisting: true}
|
||||
accountTransit, err := r.core.Accounts.New(accMsg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
account, err := r.core.Accounts.Get(accountTransit.Location)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &Resource{URI: accountTransit.Location, Body: account}, nil
|
||||
}
|
13
vendor/github.com/go-acme/lego/v3/registration/user.go
generated
vendored
13
vendor/github.com/go-acme/lego/v3/registration/user.go
generated
vendored
@ -1,13 +0,0 @@
|
||||
package registration
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
)
|
||||
|
||||
// User interface is to be implemented by users of this library.
|
||||
// It is used by the client type to get user specific information.
|
||||
type User interface {
|
||||
GetEmail() string
|
||||
GetRegistration() *Resource
|
||||
GetPrivateKey() crypto.PrivateKey
|
||||
}
|
167
vendor/github.com/klauspost/cpuid/cpuid.go
generated
vendored
167
vendor/github.com/klauspost/cpuid/cpuid.go
generated
vendored
@ -10,7 +10,13 @@
|
||||
// Package home: https://github.com/klauspost/cpuid
|
||||
package cpuid
|
||||
|
||||
import "strings"
|
||||
import (
|
||||
"math"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// AMD refererence: https://www.amd.com/system/files/TechDocs/25481.pdf
|
||||
// and Processor Programming Reference (PPR)
|
||||
|
||||
// Vendor is a representation of a CPU vendor.
|
||||
type Vendor int
|
||||
@ -28,6 +34,8 @@ const (
|
||||
XenHVM
|
||||
Bhyve
|
||||
Hygon
|
||||
SiS
|
||||
RDC
|
||||
)
|
||||
|
||||
const (
|
||||
@ -171,6 +179,7 @@ var flagNames = map[Flags]string{
|
||||
type CPUInfo struct {
|
||||
BrandName string // Brand name reported by the CPU
|
||||
VendorID Vendor // Comparable CPU vendor ID
|
||||
VendorString string // Raw vendor string.
|
||||
Features Flags // Features of the CPU
|
||||
PhysicalCores int // Number of physical processor cores in your CPU. Will be 0 if undetectable.
|
||||
ThreadsPerCore int // Number of threads per physical core. Will be 1 if undetectable.
|
||||
@ -178,11 +187,12 @@ type CPUInfo struct {
|
||||
Family int // CPU family number
|
||||
Model int // CPU model number
|
||||
CacheLine int // Cache line size in bytes. Will be 0 if undetectable.
|
||||
Hz int64 // Clock speed, if known
|
||||
Cache struct {
|
||||
L1I int // L1 Instruction Cache (per core or shared). Will be -1 if undetected
|
||||
L1D int // L1 Data Cache (per core or shared). Will be -1 if undetected
|
||||
L2 int // L2 Cache (per core or shared). Will be -1 if undetected
|
||||
L3 int // L3 Instruction Cache (per core or shared). Will be -1 if undetected
|
||||
L3 int // L3 Cache (per core, per ccx or shared). Will be -1 if undetected
|
||||
}
|
||||
SGX SGXSupport
|
||||
maxFunc uint32
|
||||
@ -224,7 +234,8 @@ func Detect() {
|
||||
CPU.ThreadsPerCore = threadsPerCore()
|
||||
CPU.LogicalCores = logicalCores()
|
||||
CPU.PhysicalCores = physicalCores()
|
||||
CPU.VendorID = vendorID()
|
||||
CPU.VendorID, CPU.VendorString = vendorID()
|
||||
CPU.Hz = hertz(CPU.BrandName)
|
||||
CPU.cacheSize()
|
||||
}
|
||||
|
||||
@ -601,6 +612,65 @@ func (c CPUInfo) LogicalCPU() int {
|
||||
return int(ebx >> 24)
|
||||
}
|
||||
|
||||
// hertz tries to compute the clock speed of the CPU. If leaf 15 is
|
||||
// supported, use it, otherwise parse the brand string. Yes, really.
|
||||
func hertz(model string) int64 {
|
||||
mfi := maxFunctionID()
|
||||
if mfi >= 0x15 {
|
||||
eax, ebx, ecx, _ := cpuid(0x15)
|
||||
if eax != 0 && ebx != 0 && ecx != 0 {
|
||||
return int64((int64(ecx) * int64(ebx)) / int64(eax))
|
||||
}
|
||||
}
|
||||
// computeHz determines the official rated speed of a CPU from its brand
|
||||
// string. This insanity is *actually the official documented way to do
|
||||
// this according to Intel*, prior to leaf 0x15 existing. The official
|
||||
// documentation only shows this working for exactly `x.xx` or `xxxx`
|
||||
// cases, e.g., `2.50GHz` or `1300MHz`; this parser will accept other
|
||||
// sizes.
|
||||
hz := strings.LastIndex(model, "Hz")
|
||||
if hz < 3 {
|
||||
return -1
|
||||
}
|
||||
var multiplier int64
|
||||
switch model[hz-1] {
|
||||
case 'M':
|
||||
multiplier = 1000 * 1000
|
||||
case 'G':
|
||||
multiplier = 1000 * 1000 * 1000
|
||||
case 'T':
|
||||
multiplier = 1000 * 1000 * 1000 * 1000
|
||||
}
|
||||
if multiplier == 0 {
|
||||
return -1
|
||||
}
|
||||
freq := int64(0)
|
||||
divisor := int64(0)
|
||||
decimalShift := int64(1)
|
||||
var i int
|
||||
for i = hz - 2; i >= 0 && model[i] != ' '; i-- {
|
||||
if model[i] >= '0' && model[i] <= '9' {
|
||||
freq += int64(model[i]-'0') * decimalShift
|
||||
decimalShift *= 10
|
||||
} else if model[i] == '.' {
|
||||
if divisor != 0 {
|
||||
return -1
|
||||
}
|
||||
divisor = decimalShift
|
||||
} else {
|
||||
return -1
|
||||
}
|
||||
}
|
||||
// we didn't find a space
|
||||
if i < 0 {
|
||||
return -1
|
||||
}
|
||||
if divisor != 0 {
|
||||
return (freq * multiplier) / divisor
|
||||
}
|
||||
return freq * multiplier
|
||||
}
|
||||
|
||||
// VM Will return true if the cpu id indicates we are in
|
||||
// a virtual machine. This is only a hint, and will very likely
|
||||
// have many false negatives.
|
||||
@ -659,11 +729,16 @@ func brandName() string {
|
||||
|
||||
func threadsPerCore() int {
|
||||
mfi := maxFunctionID()
|
||||
if mfi < 0x4 || vendorID() != Intel {
|
||||
vend, _ := vendorID()
|
||||
|
||||
if mfi < 0x4 || (vend != Intel && vend != AMD) {
|
||||
return 1
|
||||
}
|
||||
|
||||
if mfi < 0xb {
|
||||
if vend != Intel {
|
||||
return 1
|
||||
}
|
||||
_, b, _, d := cpuid(1)
|
||||
if (d & (1 << 28)) != 0 {
|
||||
// v will contain logical core count
|
||||
@ -688,7 +763,8 @@ func threadsPerCore() int {
|
||||
|
||||
func logicalCores() int {
|
||||
mfi := maxFunctionID()
|
||||
switch vendorID() {
|
||||
v, _ := vendorID()
|
||||
switch v {
|
||||
case Intel:
|
||||
// Use this on old Intel processors
|
||||
if mfi < 0xb {
|
||||
@ -723,10 +799,18 @@ func familyModel() (int, int) {
|
||||
}
|
||||
|
||||
func physicalCores() int {
|
||||
switch vendorID() {
|
||||
v, _ := vendorID()
|
||||
switch v {
|
||||
case Intel:
|
||||
return logicalCores() / threadsPerCore()
|
||||
case AMD, Hygon:
|
||||
lc := logicalCores()
|
||||
tpc := threadsPerCore()
|
||||
if lc > 0 && tpc > 0 {
|
||||
return lc / tpc
|
||||
}
|
||||
// The following is inaccurate on AMD EPYC 7742 64-Core Processor
|
||||
|
||||
if maxExtendedFunction() >= 0x80000008 {
|
||||
_, _, c, _ := cpuid(0x80000008)
|
||||
return int(c&0xff) + 1
|
||||
@ -751,16 +835,20 @@ var vendorMapping = map[string]Vendor{
|
||||
"XenVMMXenVMM": XenHVM,
|
||||
"bhyve bhyve ": Bhyve,
|
||||
"HygonGenuine": Hygon,
|
||||
"Vortex86 SoC": SiS,
|
||||
"SiS SiS SiS ": SiS,
|
||||
"RiseRiseRise": SiS,
|
||||
"Genuine RDC": RDC,
|
||||
}
|
||||
|
||||
func vendorID() Vendor {
|
||||
func vendorID() (Vendor, string) {
|
||||
_, b, c, d := cpuid(0)
|
||||
v := valAsString(b, d, c)
|
||||
vend, ok := vendorMapping[string(v)]
|
||||
v := string(valAsString(b, d, c))
|
||||
vend, ok := vendorMapping[v]
|
||||
if !ok {
|
||||
return Other
|
||||
return Other, v
|
||||
}
|
||||
return vend
|
||||
return vend, v
|
||||
}
|
||||
|
||||
func cacheLine() int {
|
||||
@ -783,7 +871,7 @@ func (c *CPUInfo) cacheSize() {
|
||||
c.Cache.L1I = -1
|
||||
c.Cache.L2 = -1
|
||||
c.Cache.L3 = -1
|
||||
vendor := vendorID()
|
||||
vendor, _ := vendorID()
|
||||
switch vendor {
|
||||
case Intel:
|
||||
if maxFunctionID() < 4 {
|
||||
@ -837,6 +925,49 @@ func (c *CPUInfo) cacheSize() {
|
||||
}
|
||||
_, _, ecx, _ = cpuid(0x80000006)
|
||||
c.Cache.L2 = int(((ecx >> 16) & 0xFFFF) * 1024)
|
||||
|
||||
// CPUID Fn8000_001D_EAX_x[N:0] Cache Properties
|
||||
if maxExtendedFunction() < 0x8000001D {
|
||||
return
|
||||
}
|
||||
for i := uint32(0); i < math.MaxUint32; i++ {
|
||||
eax, ebx, ecx, _ := cpuidex(0x8000001D, i)
|
||||
|
||||
level := (eax >> 5) & 7
|
||||
cacheNumSets := ecx + 1
|
||||
cacheLineSize := 1 + (ebx & 2047)
|
||||
cachePhysPartitions := 1 + ((ebx >> 12) & 511)
|
||||
cacheNumWays := 1 + ((ebx >> 22) & 511)
|
||||
|
||||
typ := eax & 15
|
||||
size := int(cacheNumSets * cacheLineSize * cachePhysPartitions * cacheNumWays)
|
||||
if typ == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
switch level {
|
||||
case 1:
|
||||
switch typ {
|
||||
case 1:
|
||||
// Data cache
|
||||
c.Cache.L1D = size
|
||||
case 2:
|
||||
// Inst cache
|
||||
c.Cache.L1I = size
|
||||
default:
|
||||
if c.Cache.L1D < 0 {
|
||||
c.Cache.L1I = size
|
||||
}
|
||||
if c.Cache.L1I < 0 {
|
||||
c.Cache.L1I = size
|
||||
}
|
||||
}
|
||||
case 2:
|
||||
c.Cache.L2 = size
|
||||
case 3:
|
||||
c.Cache.L3 = size
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
@ -895,7 +1026,7 @@ func hasSGX(available, lc bool) (rval SGXSupport) {
|
||||
|
||||
func support() Flags {
|
||||
mfi := maxFunctionID()
|
||||
vend := vendorID()
|
||||
vend, _ := vendorID()
|
||||
if mfi < 0x1 {
|
||||
return 0
|
||||
}
|
||||
@ -954,7 +1085,11 @@ func support() Flags {
|
||||
rval |= HTT
|
||||
}
|
||||
}
|
||||
|
||||
if vend == AMD && (d&(1<<28)) != 0 && mfi >= 4 {
|
||||
if threadsPerCore() > 1 {
|
||||
rval |= HTT
|
||||
}
|
||||
}
|
||||
// Check XGETBV, OXSAVE and AVX bits
|
||||
if c&(1<<26) != 0 && c&(1<<27) != 0 && c&(1<<28) != 0 {
|
||||
// Check for OS support
|
||||
@ -1119,7 +1254,7 @@ func support() Flags {
|
||||
AV_CPU_FLAG_SSE2 and AV_CPU_FLAG_SSE2SLOW are both set in this case
|
||||
so that SSE2 is used unless explicitly disabled by checking
|
||||
AV_CPU_FLAG_SSE2SLOW. */
|
||||
if vendorID() != Intel &&
|
||||
if vend != Intel &&
|
||||
rval&SSE2 != 0 && (c&0x00000040) == 0 {
|
||||
rval |= SSE2SLOW
|
||||
}
|
||||
@ -1135,7 +1270,7 @@ func support() Flags {
|
||||
}
|
||||
}
|
||||
|
||||
if vendorID() == Intel {
|
||||
if vend == Intel {
|
||||
family, model := familyModel()
|
||||
if family == 6 && (model == 9 || model == 13 || model == 14) {
|
||||
/* 6/9 (pentium-m "banias"), 6/13 (pentium-m "dothan"), and
|
||||
|
3
vendor/github.com/klauspost/cpuid/go.mod
generated
vendored
Normal file
3
vendor/github.com/klauspost/cpuid/go.mod
generated
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
module github.com/klauspost/cpuid
|
||||
|
||||
go 1.12
|
1
vendor/github.com/libdns/libdns/.gitignore
generated
vendored
Normal file
1
vendor/github.com/libdns/libdns/.gitignore
generated
vendored
Normal file
@ -0,0 +1 @@
|
||||
_gitignore/
|
21
vendor/github.com/libdns/libdns/LICENSE
generated
vendored
Normal file
21
vendor/github.com/libdns/libdns/LICENSE
generated
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2020 Matthew Holt
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
48
vendor/github.com/libdns/libdns/README.md
generated
vendored
Normal file
48
vendor/github.com/libdns/libdns/README.md
generated
vendored
Normal file
@ -0,0 +1,48 @@
|
||||
libdns - Universal DNS provider APIs for Go
|
||||
===========================================
|
||||
|
||||
<a href="https://pkg.go.dev/github.com/libdns/libdns"><img src="https://img.shields.io/badge/godoc-reference-blue.svg"></a>
|
||||
|
||||
`libdns` is a collection of free-range DNS provider client implementations written in Go! With libdns packages, your Go program can manage DNS records across any supported providers.
|
||||
|
||||
**⚠️ Work-in-progress. Exported APIs are subject to change. More documentation is coming soon.**
|
||||
|
||||
This repository defines the core interfaces that providers should implement. They are small and idiomatic Go interfaces with well-defined semantics.
|
||||
|
||||
The interfaces include:
|
||||
|
||||
- `RecordGetter` to list records.
|
||||
- `RecordAppender` to append new records.
|
||||
- `RecordSetter` to set (create or change existing) records.
|
||||
- `RecordDeleter` to delete records.
|
||||
|
||||
|
||||
## Implementing new providers
|
||||
|
||||
Providers are 100% written and maintained by the community! We all maintain just the packages for providers we use.
|
||||
|
||||
**[Instructions for adding new providers](https://github.com/libdns/libdns/wiki/Implementing-providers)** are on this repo's wiki. Please feel free to contribute.
|
||||
|
||||
|
||||
## Similar projects
|
||||
|
||||
**[OctoDNS](https://github.com/github/octodns)** is a suite of tools written in Python for managing DNS. However, its approach is a bit heavy-handed when all you need are small, incremental changes to a zone:
|
||||
|
||||
> WARNING: OctoDNS assumes ownership of any domain you point it to. When you tell it to act it will do whatever is necessary to try and match up states including deleting any unexpected records. Be careful when playing around with OctoDNS.
|
||||
|
||||
This is incredibly useful when you are maintaining your own zone file, but risky when you just need incremental changes.
|
||||
|
||||
**[StackExchange/dnscontrol](https://github.com/StackExchange/dnscontrol)** is written in Go, but is similar to OctoDNS in that it tends to obliterate your entire zone and replace it with your input. Again, this is very useful if you are maintaining your own master list of records, but doesn't do well for simply adding or removing records.
|
||||
|
||||
**[go-acme/lego](https://github.com/go-acme/lego)** has support for a huge number of DNS providers (75+!), but their APIs are only capable of setting and deleting TXT records for ACME challenges.
|
||||
|
||||
**`libdns`** takes inspiration from the above projects but aims for a more generally-useful set of APIs that homogenize pretty well across providers. In contrast to the above projects, libdns can add, set, delete, and get arbitrary records from a zone without obliterating it (although syncing up an entire zone is also possible!). Its APIs also include context so long-running calls can be cancelled early, for example to accommodate on-line config changes downstream. libdns interfaces are also smaller and more composable. Additionally, libdns can grow to support a nearly infinite number of DNS providers without added bloat, because each provider implementation is a separate Go module, which keeps your builds lean and fast.
|
||||
|
||||
In summary, the goal is that libdns providers can do what the above libraries/tools can do, but with more flexibility: they can create and delete TXT records for ACME challenges, they can replace entire zones, but they can also do incremental changes or simply read records.
|
||||
|
||||
|
||||
## Record abstraction
|
||||
|
||||
How records are represented across providers varies widely, and each kind of record has different fields and semantics. In time, our goal is for the `libdns.Record` type to be able to represent most of them as concisely and simply as possible, with the interface methods able to deliver on most of the possible zone operations.
|
||||
|
||||
Realistically, libdns should enable most common record manipulations, but may not be able to fit absolutely 100% of all possibilities with DNS in a provider-agnostic way. That is probably OK; and given the wide varieties in DNS record types and provider APIs, it would be unreasonable to expect otherwise. We are not aiming for 100% fulfillment of 100% of users' requirements; more like 100% fulfillment of ~90% of users' requirements.
|
3
vendor/github.com/libdns/libdns/go.mod
generated
vendored
Normal file
3
vendor/github.com/libdns/libdns/go.mod
generated
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
module github.com/libdns/libdns
|
||||
|
||||
go 1.14
|
85
vendor/github.com/libdns/libdns/libdns.go
generated
vendored
Normal file
85
vendor/github.com/libdns/libdns/libdns.go
generated
vendored
Normal file
@ -0,0 +1,85 @@
|
||||
// Package libdns defines the core interfaces that should be implemented
|
||||
// by DNS provider clients. They are small and idiomatic Go interfaces with
|
||||
// well-defined semantics.
|
||||
//
|
||||
// All interface implementations must be safe for concurrent/parallel use.
|
||||
// For example, if AppendRecords() is called at the same time and two API
|
||||
// requests are made to the provider at the same time, the result of both
|
||||
// requests must be visible after they both complete; if the provider does
|
||||
// not synchronize the writing of the zone file and one request overwrites
|
||||
// the other, then the client implementation must take care to synchronize
|
||||
// on behalf of the incompetent provider. This synchronization need not be
|
||||
// global, for example: the scope of synchronization might only need to be
|
||||
// within the same zone, allowing multiple requests at once as long as all
|
||||
// of them are for different zones. (Exact logic depends on the provider.)
|
||||
package libdns
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
)
|
||||
|
||||
// RecordGetter can get records from a DNS zone.
|
||||
type RecordGetter interface {
|
||||
// GetRecords returns all the records in the DNS zone.
|
||||
//
|
||||
// Implementations must honor context cancellation and be safe for
|
||||
// concurrent use.
|
||||
GetRecords(ctx context.Context, zone string) ([]Record, error)
|
||||
}
|
||||
|
||||
// RecordAppender can non-destructively add new records to a DNS zone.
|
||||
type RecordAppender interface {
|
||||
// AppendRecords creates the requested records in the given zone
|
||||
// and returns the populated records that were created. It never
|
||||
// changes existing records.
|
||||
//
|
||||
// Implementations must honor context cancellation and be safe for
|
||||
// concurrent use.
|
||||
AppendRecords(ctx context.Context, zone string, recs []Record) ([]Record, error)
|
||||
}
|
||||
|
||||
// RecordSetter can set new or update existing records in a DNS zone.
|
||||
type RecordSetter interface {
|
||||
// SetRecords updates the zone so that the records described in the
|
||||
// input are reflected in the output. It may create or overwrite
|
||||
// records or -- depending on the record type -- delete records to
|
||||
// maintain parity with the input. No other records are affected.
|
||||
// It returns the records which were set.
|
||||
//
|
||||
// Records that have an ID associating it with a particular resource
|
||||
// on the provider will be directly replaced. If no ID is given, this
|
||||
// method may use what information is given to do lookups and will
|
||||
// ensure that only necessary changes are made to the zone.
|
||||
//
|
||||
// Implementations must honor context cancellation and be safe for
|
||||
// concurrent use.
|
||||
SetRecords(ctx context.Context, zone string, recs []Record) ([]Record, error)
|
||||
}
|
||||
|
||||
// RecordDeleter can delete records from a DNS zone.
|
||||
type RecordDeleter interface {
|
||||
// DeleteRecords deletes the given records from the zone if they exist.
|
||||
// It returns the records that were deleted.
|
||||
//
|
||||
// Records that have an ID to associate it with a particular resource on
|
||||
// the provider will be directly deleted. If no ID is given, this method
|
||||
// may use what information is given to do lookups and delete only
|
||||
// matching records.
|
||||
//
|
||||
// Implementations must honor context cancellation and be safe for
|
||||
// concurrent use.
|
||||
DeleteRecords(ctx context.Context, zone string, recs []Record) ([]Record, error)
|
||||
}
|
||||
|
||||
// Record is a generalized representation of a DNS record.
|
||||
type Record struct {
|
||||
// provider-specific metadata
|
||||
ID string
|
||||
|
||||
// general record fields
|
||||
Type string
|
||||
Name string
|
||||
Value string
|
||||
TTL time.Duration
|
||||
}
|
1
vendor/github.com/mholt/acmez/.gitignore
generated
vendored
Normal file
1
vendor/github.com/mholt/acmez/.gitignore
generated
vendored
Normal file
@ -0,0 +1 @@
|
||||
_gitignore/
|
201
vendor/github.com/mholt/acmez/LICENSE
generated
vendored
Normal file
201
vendor/github.com/mholt/acmez/LICENSE
generated
vendored
Normal file
@ -0,0 +1,201 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
59
vendor/github.com/mholt/acmez/README.md
generated
vendored
Normal file
59
vendor/github.com/mholt/acmez/README.md
generated
vendored
Normal file
@ -0,0 +1,59 @@
|
||||
acmez - ACME client library for Go
|
||||
==================================
|
||||
|
||||
[](https://pkg.go.dev/github.com/mholt/acmez)
|
||||
|
||||
ACMEz ("ack-measy" or "acme-zee", whichever you prefer) is a fully-compliant [RFC 8555](https://tools.ietf.org/html/rfc8555) (ACME) implementation in pure Go. It is lightweight, has an elegant Go API, and its retry logic is highly robust against external errors. ACMEz is suitable for large-scale enterprise deployments.
|
||||
|
||||
**NOTE:** This module is for _getting_ certificates, not _managing_ certificates. Most users probably want certificate _management_ (keeping certificates renewed) rather than to interface directly with ACME. Developers who want to use certificates in their long-running Go programs should use [CertMagic](https://github.com/caddyserver/certmagic) instead; or, if their program is not written in Go, [Caddy](https://caddyserver.com/) can be used to manage certificates (even without running an HTTP or TLS server).
|
||||
|
||||
This module has two primary packages:
|
||||
|
||||
- **`acmez`** is a high-level wrapper for getting certificates. It implements the ACME order flow described in RFC 8555 including challenge solving using pluggable solvers.
|
||||
- **`acme`** is a low-level RFC 8555 implementation that provides the fundamental ACME operations, mainly useful if you have advanced or niche requirements.
|
||||
|
||||
In other words, the `acmez` package is **porcelain** while the `acme` package is **plumbing** (to use git's terminology).
|
||||
|
||||
|
||||
## Features
|
||||
|
||||
- Simple, elegant Go API
|
||||
- Thoroughly documented with spec citations
|
||||
- Robust to external errors
|
||||
- Structured error values ("problems" as defined in RFC 7807)
|
||||
- Smart retries (resilient against network and server hiccups)
|
||||
- Challenge plasticity (randomized challenges, and will retry others if one fails)
|
||||
- Context cancellation (suitable for high-frequency config changes or reloads)
|
||||
- Highly flexible and customizable
|
||||
- External Account Binding (EAB) support
|
||||
- Tested with multiple ACME CAs (more than just Let's Encrypt)
|
||||
- Supports niche aspects of RFC 8555 (such as alt cert chains and account key rollover)
|
||||
- Efficient solving of large SAN lists (e.g. for slow DNS record propagation)
|
||||
- Utility functions for solving challenges
|
||||
- Helpers for RFC 8737 (tls-alpn-01 challenge)
|
||||
|
||||
The `acmez` package is "bring-your-own-solver." It provides helper utilities for http-01, dns-01, and tls-alpn-01 challenges, but does not actually solve them for you. You must write an implementation of `acmez.Solver` in order to get certificates. How this is done depends on the environment in which you're using this code.
|
||||
|
||||
This is not a command line utility either. The goal is to not add more external tooling to already-complex infrastructure: ACME and TLS should be built-in to servers rather than tacked on as an afterthought.
|
||||
|
||||
|
||||
## Examples
|
||||
|
||||
See the `examples` folder for tutorials on how to use either package.
|
||||
|
||||
|
||||
## History
|
||||
|
||||
In 2014, the ISRG was finishing the development of its automated CA infrastructure: the first of its kind to become publicly-trusted, under the name Let's Encrypt, which used a young protocol called ACME to automate domain validation and certificate issuance.
|
||||
|
||||
Meanwhile, a project called [Caddy](https://caddyserver.com) was being developed which would be the first and only web server to use HTTPS _automatically and by default_. To make that possible, another project called lego was commissioned by the Caddy project to become of the first-ever ACME client libraries, and the first client written in Go. It was made by Sebastian Erhart (xenolf), and on day 1 of Let's Encrypt's public beta, Caddy used lego to obtain its first certificate automatically at startup, making Caddy and lego the first-ever integrated ACME client.
|
||||
|
||||
Since then, Caddy has seen use in production longer than any other ACME client integration, and is well-known for being one of the most robust and reliable HTTPS implementations available today.
|
||||
|
||||
A few years later, Caddy's novel auto-HTTPS logic was extracted into a library called [CertMagic](https://github.com/caddyserver/certmagic) to be usable by any Go program. Caddy would continue to use CertMagic, which implemented the certificate _automation and management_ logic on top of the low-level certificate _obtain_ logic that lego provided.
|
||||
|
||||
Soon thereafter, the lego project shifted maintainership and the goals and vision of the project diverged from those of Caddy's use case of managing tens of thousands of certificates per instance. Eventually, [the original Caddy author announced work on a new ACME client library in Go](https://github.com/caddyserver/certmagic/issues/71) that exceeded Caddy's harsh requirements for large-scale enterprise deployments, lean builds, and simple API. This work finally came to fruition in 2020 as ACMEz.
|
||||
|
||||
---
|
||||
|
||||
(c) 2020 Matthew Holt
|
37
vendor/github.com/mholt/acmez/THIRD-PARTY
generated
vendored
Normal file
37
vendor/github.com/mholt/acmez/THIRD-PARTY
generated
vendored
Normal file
@ -0,0 +1,37 @@
|
||||
This document contains Third Party Software Notices and/or Additional
|
||||
Terms and Conditions for licensed third party software components
|
||||
included within this product.
|
||||
|
||||
==
|
||||
|
||||
https://github.com/golang/crypto/blob/master/acme/jws.go
|
||||
https://github.com/golang/crypto/blob/master/acme/jws_test.go
|
||||
(with modifications)
|
||||
|
||||
Copyright (c) 2009 The Go Authors. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following disclaimer
|
||||
in the documentation and/or other materials provided with the
|
||||
distribution.
|
||||
* Neither the name of Google Inc. nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
249
vendor/github.com/mholt/acmez/acme/account.go
generated
vendored
Normal file
249
vendor/github.com/mholt/acmez/acme/account.go
generated
vendored
Normal file
@ -0,0 +1,249 @@
|
||||
// Copyright 2020 Matthew Holt
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package acme
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// Account represents a set of metadata associated with an account
|
||||
// as defined by the ACME spec §7.1.2:
|
||||
// https://tools.ietf.org/html/rfc8555#section-7.1.2
|
||||
type Account struct {
|
||||
// status (required, string): The status of this account. Possible
|
||||
// values are "valid", "deactivated", and "revoked". The value
|
||||
// "deactivated" should be used to indicate client-initiated
|
||||
// deactivation whereas "revoked" should be used to indicate server-
|
||||
// initiated deactivation. See Section 7.1.6.
|
||||
Status string `json:"status"`
|
||||
|
||||
// contact (optional, array of string): An array of URLs that the
|
||||
// server can use to contact the client for issues related to this
|
||||
// account. For example, the server may wish to notify the client
|
||||
// about server-initiated revocation or certificate expiration. For
|
||||
// information on supported URL schemes, see Section 7.3.
|
||||
Contact []string `json:"contact,omitempty"`
|
||||
|
||||
// termsOfServiceAgreed (optional, boolean): Including this field in a
|
||||
// newAccount request, with a value of true, indicates the client's
|
||||
// agreement with the terms of service. This field cannot be updated
|
||||
// by the client.
|
||||
TermsOfServiceAgreed bool `json:"termsOfServiceAgreed,omitempty"`
|
||||
|
||||
// externalAccountBinding (optional, object): Including this field in a
|
||||
// newAccount request indicates approval by the holder of an existing
|
||||
// non-ACME account to bind that account to this ACME account. This
|
||||
// field is not updateable by the client (see Section 7.3.4).
|
||||
//
|
||||
// Use SetExternalAccountBinding() to set this field's value properly.
|
||||
ExternalAccountBinding json.RawMessage `json:"externalAccountBinding,omitempty"`
|
||||
|
||||
// orders (required, string): A URL from which a list of orders
|
||||
// submitted by this account can be fetched via a POST-as-GET
|
||||
// request, as described in Section 7.1.2.1.
|
||||
Orders string `json:"orders"`
|
||||
|
||||
// In response to new-account, "the server returns this account
|
||||
// object in a 201 (Created) response, with the account URL
|
||||
// in a Location header field." §7.3
|
||||
//
|
||||
// We transfer the value from the header to this field for
|
||||
// storage and recall purposes.
|
||||
Location string `json:"location,omitempty"`
|
||||
|
||||
// The private key to the account. Because it is secret, it is
|
||||
// not serialized as JSON and must be stored separately (usually
|
||||
// a PEM-encoded file).
|
||||
PrivateKey crypto.Signer `json:"-"`
|
||||
}
|
||||
|
||||
// SetExternalAccountBinding sets the ExternalAccountBinding field of the account.
|
||||
// It only sets the field value; it does not register the account with the CA. (The
|
||||
// client parameter is necessary because the EAB encoding depends on the directory.)
|
||||
func (a *Account) SetExternalAccountBinding(ctx context.Context, client *Client, eab EAB) error {
|
||||
if err := client.provision(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
macKey, err := base64.RawURLEncoding.DecodeString(eab.MACKey)
|
||||
if err != nil {
|
||||
return fmt.Errorf("base64-decoding MAC key: %w", err)
|
||||
}
|
||||
|
||||
eabJWS, err := jwsEncodeEAB(a.PrivateKey.Public(), macKey, keyID(eab.KeyID), client.dir.NewAccount)
|
||||
if err != nil {
|
||||
return fmt.Errorf("signing EAB content: %w", err)
|
||||
}
|
||||
|
||||
a.ExternalAccountBinding = eabJWS
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// NewAccount creates a new account on the ACME server.
|
||||
//
|
||||
// "A client creates a new account with the server by sending a POST
|
||||
// request to the server's newAccount URL." §7.3
|
||||
func (c *Client) NewAccount(ctx context.Context, account Account) (Account, error) {
|
||||
if err := c.provision(ctx); err != nil {
|
||||
return account, err
|
||||
}
|
||||
return c.postAccount(ctx, c.dir.NewAccount, accountObject{Account: account})
|
||||
}
|
||||
|
||||
// GetAccount looks up an account on the ACME server.
|
||||
//
|
||||
// "If a client wishes to find the URL for an existing account and does
|
||||
// not want an account to be created if one does not already exist, then
|
||||
// it SHOULD do so by sending a POST request to the newAccount URL with
|
||||
// a JWS whose payload has an 'onlyReturnExisting' field set to 'true'."
|
||||
// §7.3.1
|
||||
func (c *Client) GetAccount(ctx context.Context, account Account) (Account, error) {
|
||||
if err := c.provision(ctx); err != nil {
|
||||
return account, err
|
||||
}
|
||||
return c.postAccount(ctx, c.dir.NewAccount, accountObject{
|
||||
Account: account,
|
||||
OnlyReturnExisting: true,
|
||||
})
|
||||
}
|
||||
|
||||
// UpdateAccount updates account information on the ACME server.
|
||||
//
|
||||
// "If the client wishes to update this information in the future, it
|
||||
// sends a POST request with updated information to the account URL.
|
||||
// The server MUST ignore any updates to the 'orders' field,
|
||||
// 'termsOfServiceAgreed' field (see Section 7.3.3), the 'status' field
|
||||
// (except as allowed by Section 7.3.6), or any other fields it does not
|
||||
// recognize." §7.3.2
|
||||
//
|
||||
// This method uses the account.Location value as the account URL.
|
||||
func (c *Client) UpdateAccount(ctx context.Context, account Account) (Account, error) {
|
||||
return c.postAccount(ctx, account.Location, accountObject{Account: account})
|
||||
}
|
||||
|
||||
type keyChangeRequest struct {
|
||||
Account string `json:"account"`
|
||||
OldKey json.RawMessage `json:"oldKey"`
|
||||
}
|
||||
|
||||
// AccountKeyRollover changes an account's associated key.
|
||||
//
|
||||
// "To change the key associated with an account, the client sends a
|
||||
// request to the server containing signatures by both the old and new
|
||||
// keys." §7.3.5
|
||||
func (c *Client) AccountKeyRollover(ctx context.Context, account Account, newPrivateKey crypto.Signer) (Account, error) {
|
||||
if err := c.provision(ctx); err != nil {
|
||||
return account, err
|
||||
}
|
||||
|
||||
oldPublicKeyJWK, err := jwkEncode(account.PrivateKey.Public())
|
||||
if err != nil {
|
||||
return account, fmt.Errorf("encoding old private key: %v", err)
|
||||
}
|
||||
|
||||
keyChangeReq := keyChangeRequest{
|
||||
Account: account.Location,
|
||||
OldKey: []byte(oldPublicKeyJWK),
|
||||
}
|
||||
|
||||
innerJWS, err := jwsEncodeJSON(keyChangeReq, newPrivateKey, "", "", c.dir.KeyChange)
|
||||
if err != nil {
|
||||
return account, fmt.Errorf("encoding inner JWS: %v", err)
|
||||
}
|
||||
|
||||
_, err = c.httpPostJWS(ctx, account.PrivateKey, account.Location, c.dir.KeyChange, json.RawMessage(innerJWS), nil)
|
||||
if err != nil {
|
||||
return account, fmt.Errorf("rolling key on server: %w", err)
|
||||
}
|
||||
|
||||
account.PrivateKey = newPrivateKey
|
||||
|
||||
return account, nil
|
||||
|
||||
}
|
||||
|
||||
func (c *Client) postAccount(ctx context.Context, endpoint string, account accountObject) (Account, error) {
|
||||
// Normally, the account URL is the key ID ("kid")... except when the user
|
||||
// is trying to get the correct account URL. In that case, we must ignore
|
||||
// any existing URL we may have and not set the kid field on the request.
|
||||
// Arguably, this is a user error (spec says "If client wishes to find the
|
||||
// URL for an existing account", so why would the URL already be filled
|
||||
// out?) but it's easy enough to infer their intent and make it work.
|
||||
kid := account.Location
|
||||
if account.OnlyReturnExisting {
|
||||
kid = ""
|
||||
}
|
||||
|
||||
resp, err := c.httpPostJWS(ctx, account.PrivateKey, kid, endpoint, account, &account.Account)
|
||||
if err != nil {
|
||||
return account.Account, err
|
||||
}
|
||||
|
||||
account.Location = resp.Header.Get("Location")
|
||||
|
||||
return account.Account, nil
|
||||
}
|
||||
|
||||
type accountObject struct {
|
||||
Account
|
||||
|
||||
// If true, newAccount will be read-only, and Account.Location
|
||||
// (which holds the account URL) must be empty.
|
||||
OnlyReturnExisting bool `json:"onlyReturnExisting,omitempty"`
|
||||
}
|
||||
|
||||
// EAB (External Account Binding) contains information
|
||||
// necessary to bind or map an ACME account to some
|
||||
// other account known by the CA.
|
||||
//
|
||||
// External account bindings are "used to associate an
|
||||
// ACME account with an existing account in a non-ACME
|
||||
// system, such as a CA customer database."
|
||||
//
|
||||
// "To enable ACME account binding, the CA operating the
|
||||
// ACME server needs to provide the ACME client with a
|
||||
// MAC key and a key identifier, using some mechanism
|
||||
// outside of ACME." §7.3.4
|
||||
type EAB struct {
|
||||
// "The key identifier MUST be an ASCII string." §7.3.4
|
||||
KeyID string `json:"key_id"`
|
||||
|
||||
// "The MAC key SHOULD be provided in base64url-encoded
|
||||
// form, to maximize compatibility between non-ACME
|
||||
// provisioning systems and ACME clients." §7.3.4
|
||||
MACKey string `json:"mac_key"`
|
||||
}
|
||||
|
||||
// Possible status values. From several spec sections:
|
||||
// - Account §7.1.2 (valid, deactivated, revoked)
|
||||
// - Order §7.1.3 (pending, ready, processing, valid, invalid)
|
||||
// - Authorization §7.1.4 (pending, valid, invalid, deactivated, expired, revoked)
|
||||
// - Challenge §7.1.5 (pending, processing, valid, invalid)
|
||||
// - Status changes §7.1.6
|
||||
const (
|
||||
StatusPending = "pending"
|
||||
StatusProcessing = "processing"
|
||||
StatusValid = "valid"
|
||||
StatusInvalid = "invalid"
|
||||
StatusDeactivated = "deactivated"
|
||||
StatusExpired = "expired"
|
||||
StatusRevoked = "revoked"
|
||||
StatusReady = "ready"
|
||||
)
|
283
vendor/github.com/mholt/acmez/acme/authorization.go
generated
vendored
Normal file
283
vendor/github.com/mholt/acmez/acme/authorization.go
generated
vendored
Normal file
@ -0,0 +1,283 @@
|
||||
// Copyright 2020 Matthew Holt
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package acme
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Authorization "represents a server's authorization for
|
||||
// an account to represent an identifier. In addition to the
|
||||
// identifier, an authorization includes several metadata fields, such
|
||||
// as the status of the authorization (e.g., 'pending', 'valid', or
|
||||
// 'revoked') and which challenges were used to validate possession of
|
||||
// the identifier." §7.1.4
|
||||
type Authorization struct {
|
||||
// identifier (required, object): The identifier that the account is
|
||||
// authorized to represent.
|
||||
Identifier Identifier `json:"identifier"`
|
||||
|
||||
// status (required, string): The status of this authorization.
|
||||
// Possible values are "pending", "valid", "invalid", "deactivated",
|
||||
// "expired", and "revoked". See Section 7.1.6.
|
||||
Status string `json:"status"`
|
||||
|
||||
// expires (optional, string): The timestamp after which the server
|
||||
// will consider this authorization invalid, encoded in the format
|
||||
// specified in [RFC3339]. This field is REQUIRED for objects with
|
||||
// "valid" in the "status" field.
|
||||
Expires time.Time `json:"expires,omitempty"`
|
||||
|
||||
// challenges (required, array of objects): For pending authorizations,
|
||||
// the challenges that the client can fulfill in order to prove
|
||||
// possession of the identifier. For valid authorizations, the
|
||||
// challenge that was validated. For invalid authorizations, the
|
||||
// challenge that was attempted and failed. Each array entry is an
|
||||
// object with parameters required to validate the challenge. A
|
||||
// client should attempt to fulfill one of these challenges, and a
|
||||
// server should consider any one of the challenges sufficient to
|
||||
// make the authorization valid.
|
||||
Challenges []Challenge `json:"challenges"`
|
||||
|
||||
// wildcard (optional, boolean): This field MUST be present and true
|
||||
// for authorizations created as a result of a newOrder request
|
||||
// containing a DNS identifier with a value that was a wildcard
|
||||
// domain name. For other authorizations, it MUST be absent.
|
||||
// Wildcard domain names are described in Section 7.1.3.
|
||||
Wildcard bool `json:"wildcard,omitempty"`
|
||||
|
||||
// "The server allocates a new URL for this authorization and returns a
|
||||
// 201 (Created) response with the authorization URL in the Location
|
||||
// header field" §7.4.1
|
||||
//
|
||||
// We transfer the value from the header to this field for storage and
|
||||
// recall purposes.
|
||||
Location string `json:"-"`
|
||||
}
|
||||
|
||||
// IdentifierValue returns the Identifier.Value field, adjusted
|
||||
// according to the Wildcard field.
|
||||
func (authz Authorization) IdentifierValue() string {
|
||||
if authz.Wildcard {
|
||||
return "*." + authz.Identifier.Value
|
||||
}
|
||||
return authz.Identifier.Value
|
||||
}
|
||||
|
||||
// fillChallengeFields populates extra fields in the challenge structs so that
|
||||
// challenges can be solved without needing a bunch of unnecessary extra state.
|
||||
func (authz *Authorization) fillChallengeFields(account Account) error {
|
||||
accountThumbprint, err := jwkThumbprint(account.PrivateKey.Public())
|
||||
if err != nil {
|
||||
return fmt.Errorf("computing account JWK thumbprint: %v", err)
|
||||
}
|
||||
for i := 0; i < len(authz.Challenges); i++ {
|
||||
authz.Challenges[i].Identifier = authz.Identifier
|
||||
if authz.Challenges[i].KeyAuthorization == "" {
|
||||
authz.Challenges[i].KeyAuthorization = authz.Challenges[i].Token + "." + accountThumbprint
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// NewAuthorization creates a new authorization for an identifier using
|
||||
// the newAuthz endpoint of the directory, if available. This function
|
||||
// creates authzs out of the regular order flow.
|
||||
//
|
||||
// "Note that because the identifier in a pre-authorization request is
|
||||
// the exact identifier to be included in the authorization object, pre-
|
||||
// authorization cannot be used to authorize issuance of certificates
|
||||
// containing wildcard domain names." §7.4.1
|
||||
func (c *Client) NewAuthorization(ctx context.Context, account Account, id Identifier) (Authorization, error) {
|
||||
if err := c.provision(ctx); err != nil {
|
||||
return Authorization{}, err
|
||||
}
|
||||
if c.dir.NewAuthz == "" {
|
||||
return Authorization{}, fmt.Errorf("server does not support newAuthz endpoint")
|
||||
}
|
||||
|
||||
var authz Authorization
|
||||
resp, err := c.httpPostJWS(ctx, account.PrivateKey, account.Location, c.dir.NewAuthz, id, &authz)
|
||||
if err != nil {
|
||||
return authz, err
|
||||
}
|
||||
|
||||
authz.Location = resp.Header.Get("Location")
|
||||
|
||||
err = authz.fillChallengeFields(account)
|
||||
if err != nil {
|
||||
return authz, err
|
||||
}
|
||||
|
||||
return authz, nil
|
||||
}
|
||||
|
||||
// GetAuthorization fetches an authorization object from the server.
|
||||
//
|
||||
// "Authorization resources are created by the server in response to
|
||||
// newOrder or newAuthz requests submitted by an account key holder;
|
||||
// their URLs are provided to the client in the responses to these
|
||||
// requests."
|
||||
//
|
||||
// "When a client receives an order from the server in reply to a
|
||||
// newOrder request, it downloads the authorization resources by sending
|
||||
// POST-as-GET requests to the indicated URLs. If the client initiates
|
||||
// authorization using a request to the newAuthz resource, it will have
|
||||
// already received the pending authorization object in the response to
|
||||
// that request." §7.5
|
||||
func (c *Client) GetAuthorization(ctx context.Context, account Account, authzURL string) (Authorization, error) {
|
||||
if err := c.provision(ctx); err != nil {
|
||||
return Authorization{}, err
|
||||
}
|
||||
|
||||
var authz Authorization
|
||||
_, err := c.httpPostJWS(ctx, account.PrivateKey, account.Location, authzURL, nil, &authz)
|
||||
if err != nil {
|
||||
return authz, err
|
||||
}
|
||||
|
||||
authz.Location = authzURL
|
||||
|
||||
err = authz.fillChallengeFields(account)
|
||||
if err != nil {
|
||||
return authz, err
|
||||
}
|
||||
|
||||
return authz, nil
|
||||
}
|
||||
|
||||
// PollAuthorization polls the authorization resource endpoint until the authorization is
|
||||
// considered "finalized" which means that it either succeeded, failed, or was abandoned.
|
||||
// It blocks until that happens or until the configured timeout.
|
||||
//
|
||||
// "Usually, the validation process will take some time, so the client
|
||||
// will need to poll the authorization resource to see when it is
|
||||
// finalized."
|
||||
//
|
||||
// "For challenges where the client can tell when the server
|
||||
// has validated the challenge (e.g., by seeing an HTTP or DNS request
|
||||
// from the server), the client SHOULD NOT begin polling until it has
|
||||
// seen the validation request from the server." §7.5.1
|
||||
func (c *Client) PollAuthorization(ctx context.Context, account Account, authz Authorization) (Authorization, error) {
|
||||
start, interval, maxDuration := time.Now(), c.pollInterval(), c.pollTimeout()
|
||||
|
||||
if authz.Status != "" {
|
||||
if finalized, err := authzIsFinalized(authz); finalized {
|
||||
return authz, err
|
||||
}
|
||||
}
|
||||
|
||||
for time.Since(start) < maxDuration {
|
||||
select {
|
||||
case <-time.After(interval):
|
||||
case <-ctx.Done():
|
||||
return authz, ctx.Err()
|
||||
}
|
||||
|
||||
// get the latest authz object
|
||||
resp, err := c.httpPostJWS(ctx, account.PrivateKey, account.Location, authz.Location, nil, &authz)
|
||||
if err != nil {
|
||||
return authz, fmt.Errorf("checking authorization status: %w", err)
|
||||
}
|
||||
if finalized, err := authzIsFinalized(authz); finalized {
|
||||
return authz, err
|
||||
}
|
||||
|
||||
// "The server MUST provide information about its retry state to the
|
||||
// client via the 'error' field in the challenge and the Retry-After
|
||||
// HTTP header field in response to requests to the challenge resource."
|
||||
// §8.2
|
||||
interval, err = retryAfter(resp, interval)
|
||||
if err != nil {
|
||||
return authz, err
|
||||
}
|
||||
}
|
||||
|
||||
return authz, fmt.Errorf("authorization took too long")
|
||||
}
|
||||
|
||||
// DeactivateAuthorization deactivates an authorization on the server, which is
|
||||
// a good idea if the authorization is not going to be utilized by the client.
|
||||
//
|
||||
// "If a client wishes to relinquish its authorization to issue
|
||||
// certificates for an identifier, then it may request that the server
|
||||
// deactivate each authorization associated with it by sending POST
|
||||
// requests with the static object {"status": "deactivated"} to each
|
||||
// authorization URL." §7.5.2
|
||||
func (c *Client) DeactivateAuthorization(ctx context.Context, account Account, authzURL string) (Authorization, error) {
|
||||
if err := c.provision(ctx); err != nil {
|
||||
return Authorization{}, err
|
||||
}
|
||||
|
||||
if authzURL == "" {
|
||||
return Authorization{}, fmt.Errorf("empty authz url")
|
||||
}
|
||||
|
||||
deactivate := struct {
|
||||
Status string `json:"status"`
|
||||
}{Status: "deactivated"}
|
||||
|
||||
var authz Authorization
|
||||
_, err := c.httpPostJWS(ctx, account.PrivateKey, account.Location, authzURL, deactivate, &authz)
|
||||
authz.Location = authzURL
|
||||
|
||||
return authz, err
|
||||
}
|
||||
|
||||
// authzIsFinalized returns true if the authorization is finished,
|
||||
// whether successfully or not. If not, an error will be returned.
|
||||
// Post-valid statuses that make an authz unusable are treated as
|
||||
// errors.
|
||||
func authzIsFinalized(authz Authorization) (bool, error) {
|
||||
switch authz.Status {
|
||||
case StatusPending:
|
||||
// "Authorization objects are created in the 'pending' state." §7.1.6
|
||||
return false, nil
|
||||
|
||||
case StatusValid:
|
||||
// "If one of the challenges listed in the authorization transitions
|
||||
// to the 'valid' state, then the authorization also changes to the
|
||||
// 'valid' state." §7.1.6
|
||||
return true, nil
|
||||
|
||||
case StatusInvalid:
|
||||
// "If the client attempts to fulfill a challenge and fails, or if
|
||||
// there is an error while the authorization is still pending, then
|
||||
// the authorization transitions to the 'invalid' state." §7.1.6
|
||||
var firstProblem Problem
|
||||
for _, chal := range authz.Challenges {
|
||||
if chal.Error != nil {
|
||||
firstProblem = *chal.Error
|
||||
break
|
||||
}
|
||||
}
|
||||
firstProblem.Resource = authz
|
||||
return true, fmt.Errorf("authorization failed: %w", firstProblem)
|
||||
|
||||
case StatusExpired, StatusDeactivated, StatusRevoked:
|
||||
// Once the authorization is in the 'valid' state, it can expire
|
||||
// ('expired'), be deactivated by the client ('deactivated', see
|
||||
// Section 7.5.2), or revoked by the server ('revoked')." §7.1.6
|
||||
return true, fmt.Errorf("authorization %s", authz.Status)
|
||||
|
||||
case "":
|
||||
return false, fmt.Errorf("status unknown")
|
||||
|
||||
default:
|
||||
return true, fmt.Errorf("server set unrecognized authorization status: %s", authz.Status)
|
||||
}
|
||||
}
|
165
vendor/github.com/mholt/acmez/acme/certificate.go
generated
vendored
Normal file
165
vendor/github.com/mholt/acmez/acme/certificate.go
generated
vendored
Normal file
@ -0,0 +1,165 @@
|
||||
// Copyright 2020 Matthew Holt
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package acme
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto"
|
||||
"crypto/x509"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// Certificate represents a certificate chain, which we usually refer
|
||||
// to as "a certificate" because in practice an end-entity certificate
|
||||
// is seldom useful/practical without a chain.
|
||||
type Certificate struct {
|
||||
// The certificate resource URL as provisioned by
|
||||
// the ACME server. Some ACME servers may split
|
||||
// the chain into multiple URLs that are Linked
|
||||
// together, in which case this URL represents the
|
||||
// starting point.
|
||||
URL string `json:"url"`
|
||||
|
||||
// The PEM-encoded certificate chain, end-entity first.
|
||||
ChainPEM []byte `json:"-"`
|
||||
}
|
||||
|
||||
// GetCertificateChain downloads all available certificate chains originating from
|
||||
// the given certURL. This is to be done after an order is finalized.
|
||||
//
|
||||
// "To download the issued certificate, the client simply sends a POST-
|
||||
// as-GET request to the certificate URL."
|
||||
//
|
||||
// "The server MAY provide one or more link relation header fields
|
||||
// [RFC8288] with relation 'alternate'. Each such field SHOULD express
|
||||
// an alternative certificate chain starting with the same end-entity
|
||||
// certificate. This can be used to express paths to various trust
|
||||
// anchors. Clients can fetch these alternates and use their own
|
||||
// heuristics to decide which is optimal." §7.4.2
|
||||
func (c *Client) GetCertificateChain(ctx context.Context, account Account, certURL string) ([]Certificate, error) {
|
||||
if err := c.provision(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var chains []Certificate
|
||||
|
||||
addChain := func(certURL string) (*http.Response, error) {
|
||||
// can't pool this buffer; bytes escape scope
|
||||
buf := new(bytes.Buffer)
|
||||
|
||||
// TODO: set the Accept header? ("application/pem-certificate-chain") See end of §7.4.2
|
||||
resp, err := c.httpPostJWS(ctx, account.PrivateKey, account.Location, certURL, nil, buf)
|
||||
if err != nil {
|
||||
return resp, err
|
||||
}
|
||||
contentType := parseMediaType(resp)
|
||||
|
||||
switch contentType {
|
||||
case "application/pem-certificate-chain":
|
||||
chains = append(chains, Certificate{
|
||||
URL: certURL,
|
||||
ChainPEM: buf.Bytes(),
|
||||
})
|
||||
default:
|
||||
return resp, fmt.Errorf("unrecognized Content-Type from server: %s", contentType)
|
||||
}
|
||||
|
||||
// "For formats that can only express a single certificate, the server SHOULD
|
||||
// provide one or more "Link: rel="up"" header fields pointing to an
|
||||
// issuer or issuers so that ACME clients can build a certificate chain
|
||||
// as defined in TLS (see Section 4.4.2 of [RFC8446])." (end of §7.4.2)
|
||||
allUp := extractLinks(resp, "up")
|
||||
for _, upURL := range allUp {
|
||||
upCerts, err := c.GetCertificateChain(ctx, account, upURL)
|
||||
if err != nil {
|
||||
return resp, fmt.Errorf("retrieving next certificate in chain: %s: %w", upURL, err)
|
||||
}
|
||||
for _, upCert := range upCerts {
|
||||
chains[len(chains)-1].ChainPEM = append(chains[len(chains)-1].ChainPEM, upCert.ChainPEM...)
|
||||
}
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// always add preferred/first certificate chain
|
||||
resp, err := addChain(certURL)
|
||||
if err != nil {
|
||||
return chains, err
|
||||
}
|
||||
|
||||
// "The server MAY provide one or more link relation header fields
|
||||
// [RFC8288] with relation 'alternate'. Each such field SHOULD express
|
||||
// an alternative certificate chain starting with the same end-entity
|
||||
// certificate. This can be used to express paths to various trust
|
||||
// anchors. Clients can fetch these alternates and use their own
|
||||
// heuristics to decide which is optimal." §7.4.2
|
||||
alternates := extractLinks(resp, "alternate")
|
||||
for _, altURL := range alternates {
|
||||
resp, err = addChain(altURL)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("retrieving alternate certificate chain at %s: %w", altURL, err)
|
||||
}
|
||||
}
|
||||
|
||||
return chains, nil
|
||||
}
|
||||
|
||||
// RevokeCertificate revokes the given certificate. If the certificate key is not
|
||||
// provided, then the account key is used instead. See §7.6.
|
||||
func (c *Client) RevokeCertificate(ctx context.Context, account Account, cert *x509.Certificate, certKey crypto.Signer, reason int) error {
|
||||
if err := c.provision(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
body := struct {
|
||||
Certificate string `json:"certificate"`
|
||||
Reason int `json:"reason"`
|
||||
}{
|
||||
Certificate: base64.RawURLEncoding.EncodeToString(cert.Raw),
|
||||
Reason: reason,
|
||||
}
|
||||
|
||||
// "Revocation requests are different from other ACME requests in that
|
||||
// they can be signed with either an account key pair or the key pair in
|
||||
// the certificate." §7.6
|
||||
kid := ""
|
||||
if certKey == account.PrivateKey {
|
||||
kid = account.Location
|
||||
}
|
||||
|
||||
_, err := c.httpPostJWS(ctx, certKey, kid, c.dir.RevokeCert, body, nil)
|
||||
return err
|
||||
}
|
||||
|
||||
// Reasons for revoking a certificate, as defined
|
||||
// by RFC 5280 §5.3.1.
|
||||
// https://tools.ietf.org/html/rfc5280#section-5.3.1
|
||||
const (
|
||||
ReasonUnspecified = iota // 0
|
||||
ReasonKeyCompromise // 1
|
||||
ReasonCACompromise // 2
|
||||
ReasonAffiliationChanged // 3
|
||||
ReasonSuperseded // 4
|
||||
ReasonCessationOfOperation // 5
|
||||
ReasonCertificateHold // 6
|
||||
_ // 7 (unused)
|
||||
ReasonRemoveFromCRL // 8
|
||||
ReasonPrivilegeWithdrawn // 9
|
||||
ReasonAACompromise // 10
|
||||
)
|
133
vendor/github.com/mholt/acmez/acme/challenge.go
generated
vendored
Normal file
133
vendor/github.com/mholt/acmez/acme/challenge.go
generated
vendored
Normal file
@ -0,0 +1,133 @@
|
||||
// Copyright 2020 Matthew Holt
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package acme
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
)
|
||||
|
||||
// Challenge holds information about an ACME challenge.
|
||||
//
|
||||
// "An ACME challenge object represents a server's offer to validate a
|
||||
// client's possession of an identifier in a specific way. Unlike the
|
||||
// other objects listed above, there is not a single standard structure
|
||||
// for a challenge object. The contents of a challenge object depend on
|
||||
// the validation method being used. The general structure of challenge
|
||||
// objects and an initial set of validation methods are described in
|
||||
// Section 8." §7.1.5
|
||||
type Challenge struct {
|
||||
// "Challenge objects all contain the following basic fields..." §8
|
||||
|
||||
// type (required, string): The type of challenge encoded in the
|
||||
// object.
|
||||
Type string `json:"type"`
|
||||
|
||||
// url (required, string): The URL to which a response can be posted.
|
||||
URL string `json:"url"`
|
||||
|
||||
// status (required, string): The status of this challenge. Possible
|
||||
// values are "pending", "processing", "valid", and "invalid" (see
|
||||
// Section 7.1.6).
|
||||
Status string `json:"status"`
|
||||
|
||||
// validated (optional, string): The time at which the server validated
|
||||
// this challenge, encoded in the format specified in [RFC3339].
|
||||
// This field is REQUIRED if the "status" field is "valid".
|
||||
Validated string `json:"validated,omitempty"`
|
||||
|
||||
// error (optional, object): Error that occurred while the server was
|
||||
// validating the challenge, if any, structured as a problem document
|
||||
// [RFC7807]. Multiple errors can be indicated by using subproblems
|
||||
// Section 6.7.1. A challenge object with an error MUST have status
|
||||
// equal to "invalid".
|
||||
Error *Problem `json:"error,omitempty"`
|
||||
|
||||
// "All additional fields are specified by the challenge type." §8
|
||||
// (We also add our own for convenience.)
|
||||
|
||||
// "The token for a challenge is a string comprised entirely of
|
||||
// characters in the URL-safe base64 alphabet." §8.1
|
||||
//
|
||||
// Used by the http-01, tls-alpn-01, and dns-01 challenges.
|
||||
Token string `json:"token,omitempty"`
|
||||
|
||||
// A key authorization is a string that concatenates the token for the
|
||||
// challenge with a key fingerprint, separated by a "." character (§8.1):
|
||||
//
|
||||
// keyAuthorization = token || '.' || base64url(Thumbprint(accountKey))
|
||||
//
|
||||
// This client package automatically assembles and sets this value for you.
|
||||
KeyAuthorization string `json:"keyAuthorization,omitempty"`
|
||||
|
||||
// We attach the identifier that this challenge is associated with, which
|
||||
// may be useful information for solving a challenge. It is not part of the
|
||||
// structure as defined by the spec but is added by us to provide enough
|
||||
// information to solve the DNS-01 challenge.
|
||||
Identifier Identifier `json:"identifier,omitempty"`
|
||||
}
|
||||
|
||||
// HTTP01ResourcePath returns the URI path for solving the http-01 challenge.
|
||||
//
|
||||
// "The path at which the resource is provisioned is comprised of the
|
||||
// fixed prefix '/.well-known/acme-challenge/', followed by the 'token'
|
||||
// value in the challenge." §8.3
|
||||
func (c Challenge) HTTP01ResourcePath() string {
|
||||
return "/.well-known/acme-challenge/" + c.Token
|
||||
}
|
||||
|
||||
// DNS01TXTRecordName returns the name of the TXT record to create for
|
||||
// solving the dns-01 challenge.
|
||||
//
|
||||
// "The client constructs the validation domain name by prepending the
|
||||
// label '_acme-challenge' to the domain name being validated, then
|
||||
// provisions a TXT record with the digest value under that name." §8.4
|
||||
func (c Challenge) DNS01TXTRecordName() string {
|
||||
return "_acme-challenge." + c.Identifier.Value
|
||||
}
|
||||
|
||||
// DNS01KeyAuthorization encodes a key authorization value to be used
|
||||
// in a TXT record for the _acme-challenge DNS record.
|
||||
//
|
||||
// "A client fulfills this challenge by constructing a key authorization
|
||||
// from the 'token' value provided in the challenge and the client's
|
||||
// account key. The client then computes the SHA-256 digest [FIPS180-4]
|
||||
// of the key authorization.
|
||||
//
|
||||
// The record provisioned to the DNS contains the base64url encoding of
|
||||
// this digest." §8.4
|
||||
func (c Challenge) DNS01KeyAuthorization() string {
|
||||
h := sha256.Sum256([]byte(c.KeyAuthorization))
|
||||
return base64.RawURLEncoding.EncodeToString(h[:])
|
||||
}
|
||||
|
||||
// InitiateChallenge "indicates to the server that it is ready for the challenge
|
||||
// validation by sending an empty JSON body ('{}') carried in a POST request to
|
||||
// the challenge URL (not the authorization URL)." §7.5.1
|
||||
func (c *Client) InitiateChallenge(ctx context.Context, account Account, challenge Challenge) (Challenge, error) {
|
||||
if err := c.provision(ctx); err != nil {
|
||||
return Challenge{}, err
|
||||
}
|
||||
_, err := c.httpPostJWS(ctx, account.PrivateKey, account.Location, challenge.URL, struct{}{}, &challenge)
|
||||
return challenge, err
|
||||
}
|
||||
|
||||
// The standard or well-known ACME challenge types.
|
||||
const (
|
||||
ChallengeTypeHTTP01 = "http-01" // RFC 8555 §8.3
|
||||
ChallengeTypeDNS01 = "dns-01" // RFC 8555 §8.4
|
||||
ChallengeTypeTLSALPN01 = "tls-alpn-01" // RFC 8737 §3
|
||||
)
|
240
vendor/github.com/mholt/acmez/acme/client.go
generated
vendored
Normal file
240
vendor/github.com/mholt/acmez/acme/client.go
generated
vendored
Normal file
@ -0,0 +1,240 @@
|
||||
// Copyright 2020 Matthew Holt
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Package acme full implements the ACME protocol specification as
|
||||
// described in RFC 8555: https://tools.ietf.org/html/rfc8555.
|
||||
//
|
||||
// It is designed to work smoothly in large-scale deployments with
|
||||
// high resilience to errors and intermittent network or server issues,
|
||||
// with retries built-in at every layer of the HTTP request stack.
|
||||
//
|
||||
// NOTE: This is a low-level API. Most users will want the mholt/acmez
|
||||
// package which is more concerned with configuring challenges and
|
||||
// implementing the order flow. However, using this package directly
|
||||
// is recommended for advanced use cases having niche requirements.
|
||||
// See the examples in the examples/plumbing folder for a tutorial.
|
||||
package acme
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
// Client facilitates ACME client operations as defined by the spec.
|
||||
//
|
||||
// Because the client is synchronized for concurrent use, it should
|
||||
// not be copied.
|
||||
//
|
||||
// Many errors that are returned by a Client are likely to be of type
|
||||
// Problem as long as the ACME server returns a structured error
|
||||
// response. This package wraps errors that may be of type Problem,
|
||||
// so you can access the details using the conventional Go pattern:
|
||||
//
|
||||
// var problem Problem
|
||||
// if errors.As(err, &problem) {
|
||||
// log.Printf("Houston, we have a problem: %+v", problem)
|
||||
// }
|
||||
//
|
||||
// All Problem errors originate from the ACME server.
|
||||
type Client struct {
|
||||
// The ACME server's directory endpoint.
|
||||
Directory string
|
||||
|
||||
// Custom HTTP client.
|
||||
HTTPClient *http.Client
|
||||
|
||||
// Augmentation of the User-Agent header. Please set
|
||||
// this so that CAs can troubleshoot bugs more easily.
|
||||
UserAgent string
|
||||
|
||||
// Delay between poll attempts. Only used if server
|
||||
// does not supply a Retry-Afer header. Default: 250ms
|
||||
PollInterval time.Duration
|
||||
|
||||
// Maximum duration for polling. Default: 5m
|
||||
PollTimeout time.Duration
|
||||
|
||||
// An optional logger. Default: no logs
|
||||
Logger *zap.Logger
|
||||
|
||||
mu sync.Mutex // protects all unexported fields
|
||||
dir Directory
|
||||
nonces *stack
|
||||
}
|
||||
|
||||
// GetDirectory retrieves the directory configured at c.Directory. It is
|
||||
// NOT necessary to call this to provision the client. It is only useful
|
||||
// if you want to access a copy of the directory yourself.
|
||||
func (c *Client) GetDirectory(ctx context.Context) (Directory, error) {
|
||||
if err := c.provision(ctx); err != nil {
|
||||
return Directory{}, err
|
||||
}
|
||||
return c.dir, nil
|
||||
}
|
||||
|
||||
func (c *Client) provision(ctx context.Context) error {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
|
||||
if c.nonces == nil {
|
||||
c.nonces = new(stack)
|
||||
}
|
||||
|
||||
err := c.provisionDirectory(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("provisioning client: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Client) provisionDirectory(ctx context.Context) error {
|
||||
// don't get directory again if we already have it;
|
||||
// checking any one of the required fields will do
|
||||
if c.dir.NewNonce != "" {
|
||||
return nil
|
||||
}
|
||||
if c.Directory == "" {
|
||||
return fmt.Errorf("missing directory URL")
|
||||
}
|
||||
// prefer cached version if it's recent enough
|
||||
directoriesMu.Lock()
|
||||
defer directoriesMu.Unlock()
|
||||
if dir, ok := directories[c.Directory]; ok {
|
||||
if time.Since(dir.retrieved) < 12*time.Hour {
|
||||
c.dir = dir.Directory
|
||||
return nil
|
||||
}
|
||||
}
|
||||
_, err := c.httpReq(ctx, http.MethodGet, c.Directory, nil, &c.dir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
directories[c.Directory] = cachedDirectory{c.dir, time.Now()}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Client) nonce(ctx context.Context) (string, error) {
|
||||
nonce := c.nonces.pop()
|
||||
if nonce != "" {
|
||||
return nonce, nil
|
||||
}
|
||||
|
||||
if c.dir.NewNonce == "" {
|
||||
return "", fmt.Errorf("directory missing newNonce endpoint")
|
||||
}
|
||||
|
||||
resp, err := c.httpReq(ctx, http.MethodHead, c.dir.NewNonce, nil, nil)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("fetching new nonce from server: %w", err)
|
||||
}
|
||||
|
||||
return resp.Header.Get(replayNonce), nil
|
||||
}
|
||||
|
||||
func (c *Client) pollInterval() time.Duration {
|
||||
if c.PollInterval == 0 {
|
||||
return defaultPollInterval
|
||||
}
|
||||
return c.PollInterval
|
||||
}
|
||||
|
||||
func (c *Client) pollTimeout() time.Duration {
|
||||
if c.PollTimeout == 0 {
|
||||
return defaultPollTimeout
|
||||
}
|
||||
return c.PollTimeout
|
||||
}
|
||||
|
||||
// Directory acts as an index for the ACME server as
|
||||
// specified in the spec: "In order to help clients
|
||||
// configure themselves with the right URLs for each
|
||||
// ACME operation, ACME servers provide a directory
|
||||
// object." §7.1.1
|
||||
type Directory struct {
|
||||
NewNonce string `json:"newNonce"`
|
||||
NewAccount string `json:"newAccount"`
|
||||
NewOrder string `json:"newOrder"`
|
||||
NewAuthz string `json:"newAuthz,omitempty"`
|
||||
RevokeCert string `json:"revokeCert"`
|
||||
KeyChange string `json:"keyChange"`
|
||||
Meta *DirectoryMeta `json:"meta,omitempty"`
|
||||
}
|
||||
|
||||
// DirectoryMeta is optional extra data that may be
|
||||
// included in an ACME server directory. §7.1.1
|
||||
type DirectoryMeta struct {
|
||||
TermsOfService string `json:"termsOfService,omitempty"`
|
||||
Website string `json:"website,omitempty"`
|
||||
CAAIdentities []string `json:"caaIdentities,omitempty"`
|
||||
ExternalAccountRequired bool `json:"externalAccountRequired,omitempty"`
|
||||
}
|
||||
|
||||
// stack is a simple thread-safe stack.
|
||||
type stack struct {
|
||||
stack []string
|
||||
stackMu sync.Mutex
|
||||
}
|
||||
|
||||
func (s *stack) push(v string) {
|
||||
if v == "" {
|
||||
return
|
||||
}
|
||||
s.stackMu.Lock()
|
||||
defer s.stackMu.Unlock()
|
||||
if len(s.stack) >= 64 {
|
||||
return
|
||||
}
|
||||
s.stack = append(s.stack, v)
|
||||
}
|
||||
|
||||
func (s *stack) pop() string {
|
||||
s.stackMu.Lock()
|
||||
defer s.stackMu.Unlock()
|
||||
n := len(s.stack)
|
||||
if n == 0 {
|
||||
return ""
|
||||
}
|
||||
v := s.stack[n-1]
|
||||
s.stack = s.stack[:n-1]
|
||||
return v
|
||||
}
|
||||
|
||||
// Directories seldom (if ever) change in practice, and
|
||||
// client structs are often ephemeral, so we can cache
|
||||
// directories to speed things up a bit for the user.
|
||||
// Keyed by directory URL.
|
||||
var (
|
||||
directories = make(map[string]cachedDirectory)
|
||||
directoriesMu sync.Mutex
|
||||
)
|
||||
|
||||
type cachedDirectory struct {
|
||||
Directory
|
||||
retrieved time.Time
|
||||
}
|
||||
|
||||
// replayNonce is the header field that contains a new
|
||||
// anti-replay nonce from the server.
|
||||
const replayNonce = "Replay-Nonce"
|
||||
|
||||
const (
|
||||
defaultPollInterval = 250 * time.Millisecond
|
||||
defaultPollTimeout = 5 * time.Minute
|
||||
)
|
394
vendor/github.com/mholt/acmez/acme/http.go
generated
vendored
Normal file
394
vendor/github.com/mholt/acmez/acme/http.go
generated
vendored
Normal file
@ -0,0 +1,394 @@
|
||||
// Copyright 2020 Matthew Holt
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package acme
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
// httpPostJWS performs robust HTTP requests by JWS-encoding the JSON of input.
|
||||
// If output is specified, the response body is written into it: if the response
|
||||
// Content-Type is JSON, it will be JSON-decoded into output (which must be a
|
||||
// pointer); otherwise, if output is an io.Writer, the response body will be
|
||||
// written to it uninterpreted. In all cases, the returned response value's
|
||||
// body will have been drained and closed, so there is no need to close it again.
|
||||
// It automatically retries in the case of network, I/O, or badNonce errors.
|
||||
func (c *Client) httpPostJWS(ctx context.Context, privateKey crypto.Signer,
|
||||
kid, endpoint string, input, output interface{}) (*http.Response, error) {
|
||||
|
||||
if err := c.provision(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var resp *http.Response
|
||||
var err error
|
||||
|
||||
// we can retry on internal server errors just in case it was a hiccup,
|
||||
// but we probably don't need to retry so many times in that case
|
||||
internalServerErrors, maxInternalServerErrors := 0, 3
|
||||
|
||||
// set a hard cap on the number of retries for any other reason
|
||||
const maxAttempts = 10
|
||||
var attempts int
|
||||
for attempts = 1; attempts <= maxAttempts; attempts++ {
|
||||
if attempts > 1 {
|
||||
select {
|
||||
case <-time.After(250 * time.Millisecond):
|
||||
case <-ctx.Done():
|
||||
return nil, ctx.Err()
|
||||
}
|
||||
}
|
||||
|
||||
var nonce string // avoid shadowing err
|
||||
nonce, err = c.nonce(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var encodedPayload []byte // avoid shadowing err
|
||||
encodedPayload, err = jwsEncodeJSON(input, privateKey, keyID(kid), nonce, endpoint)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("encoding payload: %v", err)
|
||||
}
|
||||
|
||||
resp, err = c.httpReq(ctx, http.MethodPost, endpoint, encodedPayload, output)
|
||||
if err == nil {
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// "When a server rejects a request because its nonce value was
|
||||
// unacceptable (or not present), it MUST provide HTTP status code 400
|
||||
// (Bad Request), and indicate the ACME error type
|
||||
// 'urn:ietf:params:acme:error:badNonce'. An error response with the
|
||||
// 'badNonce' error type MUST include a Replay-Nonce header field with a
|
||||
// fresh nonce that the server will accept in a retry of the original
|
||||
// query (and possibly in other requests, according to the server's
|
||||
// nonce scoping policy). On receiving such a response, a client SHOULD
|
||||
// retry the request using the new nonce." §6.5
|
||||
var problem Problem
|
||||
if errors.As(err, &problem) {
|
||||
if problem.Type == ProblemTypeBadNonce {
|
||||
if c.Logger != nil {
|
||||
c.Logger.Debug("server rejected our nonce; retrying",
|
||||
zap.String("detail", problem.Detail),
|
||||
zap.Error(err))
|
||||
}
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// internal server errors *could* just be a hiccup and it may be worth
|
||||
// trying again, but not nearly so many times as for other reasons
|
||||
if resp != nil && resp.StatusCode >= 500 {
|
||||
internalServerErrors++
|
||||
if internalServerErrors < maxInternalServerErrors {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// for any other error, there's not much we can do automatically
|
||||
break
|
||||
}
|
||||
|
||||
return resp, fmt.Errorf("request to %s failed after %d attempts: %v",
|
||||
endpoint, attempts, err)
|
||||
}
|
||||
|
||||
// httpReq robustly performs an HTTP request using the given method to the given endpoint, honoring
|
||||
// the given context's cancellation. The joseJSONPayload is optional; if not nil, it is expected to
|
||||
// be a JOSE+JSON encoding. The output is also optional; if not nil, the response body will be read
|
||||
// into output. If the response Content-Type is JSON, it will be JSON-decoded into output, which
|
||||
// must be a pointer type. If the response is any other Content-Type and if output is a io.Writer,
|
||||
// it will be written (without interpretation or decoding) to output. In all cases, the returned
|
||||
// response value will have the body drained and closed, so there is no need to close it again.
|
||||
//
|
||||
// If there are any network or I/O errors, the request will be retried as safely and resiliently as
|
||||
// possible.
|
||||
func (c *Client) httpReq(ctx context.Context, method, endpoint string, joseJSONPayload []byte, output interface{}) (*http.Response, error) {
|
||||
// even if the caller doesn't specify an output, we still use a
|
||||
// buffer to store possible error response (we reset it later)
|
||||
buf := bufPool.Get().(*bytes.Buffer)
|
||||
defer bufPool.Put(buf)
|
||||
|
||||
var resp *http.Response
|
||||
var err error
|
||||
|
||||
// potentially retry the request if there's network, I/O, or server internal errors
|
||||
const maxAttempts = 3
|
||||
for attempt := 0; attempt < maxAttempts; attempt++ {
|
||||
if attempt > 0 {
|
||||
// traffic calming ahead
|
||||
select {
|
||||
case <-time.After(250 * time.Millisecond):
|
||||
case <-ctx.Done():
|
||||
return nil, ctx.Err()
|
||||
}
|
||||
}
|
||||
|
||||
var body io.Reader
|
||||
if joseJSONPayload != nil {
|
||||
body = bytes.NewReader(joseJSONPayload)
|
||||
}
|
||||
|
||||
var req *http.Request
|
||||
req, err = http.NewRequestWithContext(ctx, method, endpoint, body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("creating request: %w", err)
|
||||
}
|
||||
if len(joseJSONPayload) > 0 {
|
||||
req.Header.Set("Content-Type", "application/jose+json")
|
||||
}
|
||||
|
||||
// on first attempt, we need to reset buf since it
|
||||
// came from the pool; after first attempt, we should
|
||||
// still reset it because we might be retrying after
|
||||
// a partial download
|
||||
buf.Reset()
|
||||
|
||||
var retry bool
|
||||
resp, retry, err = c.doHTTPRequest(req, buf)
|
||||
if err != nil {
|
||||
if retry {
|
||||
if c.Logger != nil {
|
||||
c.Logger.Warn("HTTP request failed; retrying",
|
||||
zap.String("url", req.URL.String()),
|
||||
zap.Error(err))
|
||||
}
|
||||
continue
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
// check for HTTP errors
|
||||
switch {
|
||||
case resp.StatusCode >= 200 && resp.StatusCode < 300: // OK
|
||||
case resp.StatusCode >= 400 && resp.StatusCode < 600: // error
|
||||
if parseMediaType(resp) == "application/problem+json" {
|
||||
// "When the server responds with an error status, it SHOULD provide
|
||||
// additional information using a problem document [RFC7807]." (§6.7)
|
||||
var problem Problem
|
||||
err = json.Unmarshal(buf.Bytes(), &problem)
|
||||
if err != nil {
|
||||
return resp, fmt.Errorf("HTTP %d: JSON-decoding problem details: %w (raw='%s')",
|
||||
resp.StatusCode, err, buf.String())
|
||||
}
|
||||
if resp.StatusCode >= 500 && joseJSONPayload == nil {
|
||||
// a 5xx status is probably safe to retry on even after a
|
||||
// request that had no I/O errors; it could be that the
|
||||
// server just had a hiccup... so try again, but only if
|
||||
// there is no request body, because we can't replay a
|
||||
// request that has an anti-replay nonce, obviously
|
||||
err = problem
|
||||
continue
|
||||
}
|
||||
return resp, problem
|
||||
}
|
||||
return resp, fmt.Errorf("HTTP %d: %s", resp.StatusCode, buf.String())
|
||||
default: // what even is this
|
||||
return resp, fmt.Errorf("unexpected status code: HTTP %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
// do not retry if we got this far (success)
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
return resp, err
|
||||
}
|
||||
|
||||
// if expecting a body, finally decode it
|
||||
if output != nil {
|
||||
contentType := parseMediaType(resp)
|
||||
switch contentType {
|
||||
case "application/json":
|
||||
// unmarshal JSON
|
||||
err = json.Unmarshal(buf.Bytes(), output)
|
||||
if err != nil {
|
||||
return resp, fmt.Errorf("JSON-decoding response body: %w", err)
|
||||
}
|
||||
|
||||
default:
|
||||
// don't interpret anything else here; just hope
|
||||
// it's a Writer and copy the bytes
|
||||
w, ok := output.(io.Writer)
|
||||
if !ok {
|
||||
return resp, fmt.Errorf("response Content-Type is %s but target container is not io.Writer: %T", contentType, output)
|
||||
}
|
||||
_, err = io.Copy(w, buf)
|
||||
if err != nil {
|
||||
return resp, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// doHTTPRequest performs an HTTP request at most one time. It returns the response
|
||||
// (with drained and closed body), having drained any request body into buf. If
|
||||
// retry == true is returned, then the request should be safe to retry in the case
|
||||
// of an error. However, in some cases a retry may be recommended even if part of
|
||||
// the response body has been read and written into buf. Thus, the buffer may have
|
||||
// been partially written to and should be reset before being reused.
|
||||
//
|
||||
// This method remembers any nonce returned by the server.
|
||||
func (c *Client) doHTTPRequest(req *http.Request, buf *bytes.Buffer) (resp *http.Response, retry bool, err error) {
|
||||
req.Header.Set("User-Agent", c.userAgent())
|
||||
|
||||
resp, err = c.httpClient().Do(req)
|
||||
if err != nil {
|
||||
return resp, true, fmt.Errorf("performing request: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if c.Logger != nil {
|
||||
c.Logger.Debug("http request",
|
||||
zap.String("method", req.Method),
|
||||
zap.String("url", req.URL.String()),
|
||||
zap.Reflect("headers", req.Header),
|
||||
zap.Int("status_code", resp.StatusCode),
|
||||
zap.Reflect("response_headers", resp.Header))
|
||||
}
|
||||
|
||||
// "The server MUST include a Replay-Nonce header field
|
||||
// in every successful response to a POST request and
|
||||
// SHOULD provide it in error responses as well." §6.5
|
||||
//
|
||||
// "Before sending a POST request to the server, an ACME
|
||||
// client needs to have a fresh anti-replay nonce to put
|
||||
// in the 'nonce' header of the JWS. In most cases, the
|
||||
// client will have gotten a nonce from a previous
|
||||
// request." §7.2
|
||||
//
|
||||
// So basically, we need to remember the nonces we get
|
||||
// and use them at the next opportunity.
|
||||
c.nonces.push(resp.Header.Get(replayNonce))
|
||||
|
||||
// drain the response body, even if we aren't keeping it
|
||||
// (this allows us to reuse the connection and also read
|
||||
// any error information)
|
||||
_, err = io.Copy(buf, resp.Body)
|
||||
if err != nil {
|
||||
// this is likely a network or I/O error, but is it worth retrying?
|
||||
// technically the request has already completed, it was just our
|
||||
// download of the response that failed; so we probably should not
|
||||
// retry if the request succeeded... however, if there was an HTTP
|
||||
// error, it likely didn't count against any server-enforced rate
|
||||
// limits, and we DO want to know the error information, so it should
|
||||
// be safe to retry the request in those cases AS LONG AS there is
|
||||
// no request body, which in the context of ACME likely includes an
|
||||
// anti-replay nonce, which obviously we can't reuse
|
||||
retry = resp.StatusCode >= 400 && req.Body == nil
|
||||
return resp, retry, fmt.Errorf("reading response body: %w", err)
|
||||
}
|
||||
|
||||
return resp, false, nil
|
||||
}
|
||||
|
||||
func (c *Client) httpClient() *http.Client {
|
||||
if c.HTTPClient == nil {
|
||||
return http.DefaultClient
|
||||
}
|
||||
return c.HTTPClient
|
||||
}
|
||||
|
||||
func (c *Client) userAgent() string {
|
||||
ua := fmt.Sprintf("acmez (%s; %s)", runtime.GOOS, runtime.GOARCH)
|
||||
if c.UserAgent != "" {
|
||||
ua = c.UserAgent + " " + ua
|
||||
}
|
||||
return ua
|
||||
}
|
||||
|
||||
// extractLinks extracts the URL from the Link header with the
|
||||
// designated relation rel. It may return more than value
|
||||
// if there are multiple matching Link values.
|
||||
//
|
||||
// Originally by Isaac: https://github.com/eggsampler/acme
|
||||
// and has been modified to support multiple matching Links.
|
||||
func extractLinks(resp *http.Response, rel string) []string {
|
||||
if resp == nil {
|
||||
return nil
|
||||
}
|
||||
var links []string
|
||||
for _, l := range resp.Header["Link"] {
|
||||
matches := linkRegex.FindAllStringSubmatch(l, -1)
|
||||
for _, m := range matches {
|
||||
if len(m) != 3 {
|
||||
continue
|
||||
}
|
||||
if m[2] == rel {
|
||||
links = append(links, m[1])
|
||||
}
|
||||
}
|
||||
}
|
||||
return links
|
||||
}
|
||||
|
||||
// parseMediaType returns only the media type from the
|
||||
// Content-Type header of resp.
|
||||
func parseMediaType(resp *http.Response) string {
|
||||
if resp == nil {
|
||||
return ""
|
||||
}
|
||||
ct := resp.Header.Get("Content-Type")
|
||||
sep := strings.Index(ct, ";")
|
||||
if sep < 0 {
|
||||
return ct
|
||||
}
|
||||
return strings.TrimSpace(ct[:sep])
|
||||
}
|
||||
|
||||
// retryAfter returns a duration from the response's Retry-After
|
||||
// header field, if it exists. It can return an error if the
|
||||
// header contains an invalid value. If there is no error but
|
||||
// there is no Retry-After header provided, then the fallback
|
||||
// duration is returned instead.
|
||||
func retryAfter(resp *http.Response, fallback time.Duration) (time.Duration, error) {
|
||||
if resp == nil {
|
||||
return fallback, nil
|
||||
}
|
||||
raSeconds := resp.Header.Get("Retry-After")
|
||||
if raSeconds == "" {
|
||||
return fallback, nil
|
||||
}
|
||||
ra, err := strconv.Atoi(raSeconds)
|
||||
if err != nil || ra < 0 {
|
||||
return 0, fmt.Errorf("response had invalid Retry-After header: %s", raSeconds)
|
||||
}
|
||||
return time.Duration(ra) * time.Second, nil
|
||||
}
|
||||
|
||||
var bufPool = sync.Pool{
|
||||
New: func() interface{} {
|
||||
return new(bytes.Buffer)
|
||||
},
|
||||
}
|
||||
|
||||
var linkRegex = regexp.MustCompile(`<(.+?)>;\s*rel="(.+?)"`)
|
263
vendor/github.com/mholt/acmez/acme/jws.go
generated
vendored
Normal file
263
vendor/github.com/mholt/acmez/acme/jws.go
generated
vendored
Normal file
@ -0,0 +1,263 @@
|
||||
// Copyright 2020 Matthew Holt
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
// --- ORIGINAL LICENSE ---
|
||||
//
|
||||
// Copyright 2015 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the THIRD-PARTY file.
|
||||
//
|
||||
// (This file has been modified from its original contents.)
|
||||
// (And it has dragons. Don't wake the dragons.)
|
||||
|
||||
package acme
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/ecdsa"
|
||||
"crypto/hmac"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/sha256"
|
||||
_ "crypto/sha512" // need for EC keys
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"math/big"
|
||||
)
|
||||
|
||||
var errUnsupportedKey = fmt.Errorf("unknown key type; only RSA and ECDSA are supported")
|
||||
|
||||
// keyID is the account identity provided by a CA during registration.
|
||||
type keyID string
|
||||
|
||||
// noKeyID indicates that jwsEncodeJSON should compute and use JWK instead of a KID.
|
||||
// See jwsEncodeJSON for details.
|
||||
const noKeyID = keyID("")
|
||||
|
||||
// // noPayload indicates jwsEncodeJSON will encode zero-length octet string
|
||||
// // in a JWS request. This is called POST-as-GET in RFC 8555 and is used to make
|
||||
// // authenticated GET requests via POSTing with an empty payload.
|
||||
// // See https://tools.ietf.org/html/rfc8555#section-6.3 for more details.
|
||||
// const noPayload = ""
|
||||
|
||||
// jwsEncodeEAB creates a JWS payload for External Account Binding according to RFC 8555 §7.3.4.
|
||||
func jwsEncodeEAB(accountKey crypto.PublicKey, hmacKey []byte, kid keyID, url string) ([]byte, error) {
|
||||
// §7.3.4: "The 'alg' field MUST indicate a MAC-based algorithm"
|
||||
alg, sha := "HS256", crypto.SHA256
|
||||
|
||||
// §7.3.4: "The 'nonce' field MUST NOT be present"
|
||||
phead, err := jwsHead(alg, "", url, kid, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
encodedKey, err := jwkEncode(accountKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
payload := base64.RawURLEncoding.EncodeToString([]byte(encodedKey))
|
||||
|
||||
payloadToSign := []byte(phead + "." + payload)
|
||||
|
||||
h := hmac.New(sha256.New, hmacKey)
|
||||
h.Write(payloadToSign)
|
||||
sig := h.Sum(nil)
|
||||
|
||||
return jwsFinal(sha, sig, phead, payload)
|
||||
}
|
||||
|
||||
// jwsEncodeJSON signs claimset using provided key and a nonce.
|
||||
// The result is serialized in JSON format containing either kid or jwk
|
||||
// fields based on the provided keyID value.
|
||||
//
|
||||
// If kid is non-empty, its quoted value is inserted in the protected head
|
||||
// as "kid" field value. Otherwise, JWK is computed using jwkEncode and inserted
|
||||
// as "jwk" field value. The "jwk" and "kid" fields are mutually exclusive.
|
||||
//
|
||||
// See https://tools.ietf.org/html/rfc7515#section-7.
|
||||
//
|
||||
// If nonce is empty, it will not be encoded into the header.
|
||||
func jwsEncodeJSON(claimset interface{}, key crypto.Signer, kid keyID, nonce, url string) ([]byte, error) {
|
||||
alg, sha := jwsHasher(key.Public())
|
||||
if alg == "" || !sha.Available() {
|
||||
return nil, errUnsupportedKey
|
||||
}
|
||||
|
||||
phead, err := jwsHead(alg, nonce, url, kid, key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var payload string
|
||||
if claimset != nil {
|
||||
cs, err := json.Marshal(claimset)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
payload = base64.RawURLEncoding.EncodeToString(cs)
|
||||
}
|
||||
|
||||
payloadToSign := []byte(phead + "." + payload)
|
||||
hash := sha.New()
|
||||
_, _ = hash.Write(payloadToSign)
|
||||
digest := hash.Sum(nil)
|
||||
|
||||
sig, err := jwsSign(key, sha, digest)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return jwsFinal(sha, sig, phead, payload)
|
||||
}
|
||||
|
||||
// jwkEncode encodes public part of an RSA or ECDSA key into a JWK.
|
||||
// The result is also suitable for creating a JWK thumbprint.
|
||||
// https://tools.ietf.org/html/rfc7517
|
||||
func jwkEncode(pub crypto.PublicKey) (string, error) {
|
||||
switch pub := pub.(type) {
|
||||
case *rsa.PublicKey:
|
||||
// https://tools.ietf.org/html/rfc7518#section-6.3.1
|
||||
n := pub.N
|
||||
e := big.NewInt(int64(pub.E))
|
||||
// Field order is important.
|
||||
// See https://tools.ietf.org/html/rfc7638#section-3.3 for details.
|
||||
return fmt.Sprintf(`{"e":"%s","kty":"RSA","n":"%s"}`,
|
||||
base64.RawURLEncoding.EncodeToString(e.Bytes()),
|
||||
base64.RawURLEncoding.EncodeToString(n.Bytes()),
|
||||
), nil
|
||||
case *ecdsa.PublicKey:
|
||||
// https://tools.ietf.org/html/rfc7518#section-6.2.1
|
||||
p := pub.Curve.Params()
|
||||
n := p.BitSize / 8
|
||||
if p.BitSize%8 != 0 {
|
||||
n++
|
||||
}
|
||||
x := pub.X.Bytes()
|
||||
if n > len(x) {
|
||||
x = append(make([]byte, n-len(x)), x...)
|
||||
}
|
||||
y := pub.Y.Bytes()
|
||||
if n > len(y) {
|
||||
y = append(make([]byte, n-len(y)), y...)
|
||||
}
|
||||
// Field order is important.
|
||||
// See https://tools.ietf.org/html/rfc7638#section-3.3 for details.
|
||||
return fmt.Sprintf(`{"crv":"%s","kty":"EC","x":"%s","y":"%s"}`,
|
||||
p.Name,
|
||||
base64.RawURLEncoding.EncodeToString(x),
|
||||
base64.RawURLEncoding.EncodeToString(y),
|
||||
), nil
|
||||
}
|
||||
return "", errUnsupportedKey
|
||||
}
|
||||
|
||||
// jwsHead constructs the protected JWS header for the given fields.
|
||||
// Since jwk and kid are mutually-exclusive, the jwk will be encoded
|
||||
// only if kid is empty. If nonce is empty, it will not be encoded.
|
||||
func jwsHead(alg, nonce, url string, kid keyID, key crypto.Signer) (string, error) {
|
||||
phead := fmt.Sprintf(`{"alg":%q`, alg)
|
||||
if kid == noKeyID {
|
||||
jwk, err := jwkEncode(key.Public())
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
phead += fmt.Sprintf(`,"jwk":%s`, jwk)
|
||||
} else {
|
||||
phead += fmt.Sprintf(`,"kid":%q`, kid)
|
||||
}
|
||||
if nonce != "" {
|
||||
phead += fmt.Sprintf(`,"nonce":%q`, nonce)
|
||||
}
|
||||
phead += fmt.Sprintf(`,"url":%q}`, url)
|
||||
phead = base64.RawURLEncoding.EncodeToString([]byte(phead))
|
||||
return phead, nil
|
||||
}
|
||||
|
||||
// jwsFinal constructs the final JWS object.
|
||||
func jwsFinal(sha crypto.Hash, sig []byte, phead, payload string) ([]byte, error) {
|
||||
enc := struct {
|
||||
Protected string `json:"protected"`
|
||||
Payload string `json:"payload"`
|
||||
Sig string `json:"signature"`
|
||||
}{
|
||||
Protected: phead,
|
||||
Payload: payload,
|
||||
Sig: base64.RawURLEncoding.EncodeToString(sig),
|
||||
}
|
||||
result, err := json.Marshal(&enc)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// jwsSign signs the digest using the given key.
|
||||
// The hash is unused for ECDSA keys.
|
||||
//
|
||||
// Note: non-stdlib crypto.Signer implementations are expected to return
|
||||
// the signature in the format as specified in RFC7518.
|
||||
// See https://tools.ietf.org/html/rfc7518 for more details.
|
||||
func jwsSign(key crypto.Signer, hash crypto.Hash, digest []byte) ([]byte, error) {
|
||||
if key, ok := key.(*ecdsa.PrivateKey); ok {
|
||||
// The key.Sign method of ecdsa returns ASN1-encoded signature.
|
||||
// So, we use the package Sign function instead
|
||||
// to get R and S values directly and format the result accordingly.
|
||||
r, s, err := ecdsa.Sign(rand.Reader, key, digest)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rb, sb := r.Bytes(), s.Bytes()
|
||||
size := key.Params().BitSize / 8
|
||||
if size%8 > 0 {
|
||||
size++
|
||||
}
|
||||
sig := make([]byte, size*2)
|
||||
copy(sig[size-len(rb):], rb)
|
||||
copy(sig[size*2-len(sb):], sb)
|
||||
return sig, nil
|
||||
}
|
||||
return key.Sign(rand.Reader, digest, hash)
|
||||
}
|
||||
|
||||
// jwsHasher indicates suitable JWS algorithm name and a hash function
|
||||
// to use for signing a digest with the provided key.
|
||||
// It returns ("", 0) if the key is not supported.
|
||||
func jwsHasher(pub crypto.PublicKey) (string, crypto.Hash) {
|
||||
switch pub := pub.(type) {
|
||||
case *rsa.PublicKey:
|
||||
return "RS256", crypto.SHA256
|
||||
case *ecdsa.PublicKey:
|
||||
switch pub.Params().Name {
|
||||
case "P-256":
|
||||
return "ES256", crypto.SHA256
|
||||
case "P-384":
|
||||
return "ES384", crypto.SHA384
|
||||
case "P-521":
|
||||
return "ES512", crypto.SHA512
|
||||
}
|
||||
}
|
||||
return "", 0
|
||||
}
|
||||
|
||||
// jwkThumbprint creates a JWK thumbprint out of pub
|
||||
// as specified in https://tools.ietf.org/html/rfc7638.
|
||||
func jwkThumbprint(pub crypto.PublicKey) (string, error) {
|
||||
jwk, err := jwkEncode(pub)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
b := sha256.Sum256([]byte(jwk))
|
||||
return base64.RawURLEncoding.EncodeToString(b[:]), nil
|
||||
}
|
247
vendor/github.com/mholt/acmez/acme/order.go
generated
vendored
Normal file
247
vendor/github.com/mholt/acmez/acme/order.go
generated
vendored
Normal file
@ -0,0 +1,247 @@
|
||||
// Copyright 2020 Matthew Holt
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package acme
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Order is an object that "represents a client's request for a certificate
|
||||
// and is used to track the progress of that order through to issuance.
|
||||
// Thus, the object contains information about the requested
|
||||
// certificate, the authorizations that the server requires the client
|
||||
// to complete, and any certificates that have resulted from this order."
|
||||
// §7.1.3
|
||||
type Order struct {
|
||||
// status (required, string): The status of this order. Possible
|
||||
// values are "pending", "ready", "processing", "valid", and
|
||||
// "invalid". See Section 7.1.6.
|
||||
Status string `json:"status"`
|
||||
|
||||
// expires (optional, string): The timestamp after which the server
|
||||
// will consider this order invalid, encoded in the format specified
|
||||
// in [RFC3339]. This field is REQUIRED for objects with "pending"
|
||||
// or "valid" in the status field.
|
||||
Expires time.Time `json:"expires,omitempty"`
|
||||
|
||||
// identifiers (required, array of object): An array of identifier
|
||||
// objects that the order pertains to.
|
||||
Identifiers []Identifier `json:"identifiers"`
|
||||
|
||||
// notBefore (optional, string): The requested value of the notBefore
|
||||
// field in the certificate, in the date format defined in [RFC3339].
|
||||
NotBefore *time.Time `json:"notBefore,omitempty"`
|
||||
|
||||
// notAfter (optional, string): The requested value of the notAfter
|
||||
// field in the certificate, in the date format defined in [RFC3339].
|
||||
NotAfter *time.Time `json:"notAfter,omitempty"`
|
||||
|
||||
// error (optional, object): The error that occurred while processing
|
||||
// the order, if any. This field is structured as a problem document
|
||||
// [RFC7807].
|
||||
Error *Problem `json:"error,omitempty"`
|
||||
|
||||
// authorizations (required, array of string): For pending orders, the
|
||||
// authorizations that the client needs to complete before the
|
||||
// requested certificate can be issued (see Section 7.5), including
|
||||
// unexpired authorizations that the client has completed in the past
|
||||
// for identifiers specified in the order. The authorizations
|
||||
// required are dictated by server policy; there may not be a 1:1
|
||||
// relationship between the order identifiers and the authorizations
|
||||
// required. For final orders (in the "valid" or "invalid" state),
|
||||
// the authorizations that were completed. Each entry is a URL from
|
||||
// which an authorization can be fetched with a POST-as-GET request.
|
||||
Authorizations []string `json:"authorizations"`
|
||||
|
||||
// finalize (required, string): A URL that a CSR must be POSTed to once
|
||||
// all of the order's authorizations are satisfied to finalize the
|
||||
// order. The result of a successful finalization will be the
|
||||
// population of the certificate URL for the order.
|
||||
Finalize string `json:"finalize"`
|
||||
|
||||
// certificate (optional, string): A URL for the certificate that has
|
||||
// been issued in response to this order.
|
||||
Certificate string `json:"certificate"`
|
||||
|
||||
// Similar to new-account, the server returns a 201 response with
|
||||
// the URL to the order object in the Location header.
|
||||
//
|
||||
// We transfer the value from the header to this field for
|
||||
// storage and recall purposes.
|
||||
Location string `json:"-"`
|
||||
}
|
||||
|
||||
// Identifier is used in order and authorization (authz) objects.
|
||||
type Identifier struct {
|
||||
// type (required, string): The type of identifier. This document
|
||||
// defines the "dns" identifier type. See the registry defined in
|
||||
// Section 9.7.7 for any others.
|
||||
Type string `json:"type"`
|
||||
|
||||
// value (required, string): The identifier itself.
|
||||
Value string `json:"value"`
|
||||
}
|
||||
|
||||
// NewOrder creates a new order with the server.
|
||||
//
|
||||
// "The client begins the certificate issuance process by sending a POST
|
||||
// request to the server's newOrder resource." §7.4
|
||||
func (c *Client) NewOrder(ctx context.Context, account Account, order Order) (Order, error) {
|
||||
if err := c.provision(ctx); err != nil {
|
||||
return order, err
|
||||
}
|
||||
resp, err := c.httpPostJWS(ctx, account.PrivateKey, account.Location, c.dir.NewOrder, order, &order)
|
||||
if err != nil {
|
||||
return order, err
|
||||
}
|
||||
order.Location = resp.Header.Get("Location")
|
||||
return order, nil
|
||||
}
|
||||
|
||||
// FinalizeOrder finalizes the order with the server and polls under the server has
|
||||
// updated the order status. The CSR must be in ASN.1 DER-encoded format. If this
|
||||
// succeeds, the certificate is ready to download once this returns.
|
||||
//
|
||||
// "Once the client believes it has fulfilled the server's requirements,
|
||||
// it should send a POST request to the order resource's finalize URL." §7.4
|
||||
func (c *Client) FinalizeOrder(ctx context.Context, account Account, order Order, csrASN1DER []byte) (Order, error) {
|
||||
if err := c.provision(ctx); err != nil {
|
||||
return order, err
|
||||
}
|
||||
|
||||
body := struct {
|
||||
// csr (required, string): A CSR encoding the parameters for the
|
||||
// certificate being requested [RFC2986]. The CSR is sent in the
|
||||
// base64url-encoded version of the DER format. (Note: Because this
|
||||
// field uses base64url, and does not include headers, it is
|
||||
// different from PEM.) §7.4
|
||||
CSR string `json:"csr"`
|
||||
}{
|
||||
CSR: base64.RawURLEncoding.EncodeToString(csrASN1DER),
|
||||
}
|
||||
|
||||
resp, err := c.httpPostJWS(ctx, account.PrivateKey, account.Location, order.Finalize, body, &order)
|
||||
if err != nil {
|
||||
// "A request to finalize an order will result in error if the order is
|
||||
// not in the 'ready' state. In such cases, the server MUST return a
|
||||
// 403 (Forbidden) error with a problem document of type
|
||||
// 'orderNotReady'. The client should then send a POST-as-GET request
|
||||
// to the order resource to obtain its current state. The status of the
|
||||
// order will indicate what action the client should take (see below)."
|
||||
// §7.4
|
||||
var problem Problem
|
||||
if errors.As(err, &problem) {
|
||||
if problem.Type != ProblemTypeOrderNotReady {
|
||||
return order, err
|
||||
}
|
||||
} else {
|
||||
return order, err
|
||||
}
|
||||
}
|
||||
|
||||
// unlike with accounts and authorizations, the spec isn't clear on whether
|
||||
// the server MUST set this on finalizing the order, but their example shows a
|
||||
// Location header, so I guess if it's set in the response, we should keep it
|
||||
if newLocation := resp.Header.Get("Location"); newLocation != "" {
|
||||
order.Location = newLocation
|
||||
}
|
||||
|
||||
if finished, err := orderIsFinished(order); finished {
|
||||
return order, err
|
||||
}
|
||||
|
||||
// TODO: "The elements of the "authorizations" and "identifiers" arrays are
|
||||
// immutable once set. If a client observes a change
|
||||
// in the contents of either array, then it SHOULD consider the order
|
||||
// invalid."
|
||||
|
||||
maxDuration := c.pollTimeout()
|
||||
start := time.Now()
|
||||
for time.Since(start) < maxDuration {
|
||||
// querying an order is expensive on the server-side, so we
|
||||
// shouldn't do it too frequently; honor server preference
|
||||
interval, err := retryAfter(resp, c.pollInterval())
|
||||
if err != nil {
|
||||
return order, err
|
||||
}
|
||||
select {
|
||||
case <-time.After(interval):
|
||||
case <-ctx.Done():
|
||||
return order, ctx.Err()
|
||||
}
|
||||
|
||||
resp, err = c.httpPostJWS(ctx, account.PrivateKey, account.Location, order.Location, nil, &order)
|
||||
if err != nil {
|
||||
return order, fmt.Errorf("polling order status: %w", err)
|
||||
}
|
||||
|
||||
// (same reasoning as above)
|
||||
if newLocation := resp.Header.Get("Location"); newLocation != "" {
|
||||
order.Location = newLocation
|
||||
}
|
||||
|
||||
if finished, err := orderIsFinished(order); finished {
|
||||
return order, err
|
||||
}
|
||||
}
|
||||
|
||||
return order, fmt.Errorf("order took too long")
|
||||
}
|
||||
|
||||
// orderIsFinished returns true if the order processing is complete,
|
||||
// regardless of success or failure. If this function returns true,
|
||||
// polling an order status should stop. If there is an error with the
|
||||
// order, an error will be returned. This function should be called
|
||||
// only after a request to finalize an order. See §7.4.
|
||||
func orderIsFinished(order Order) (bool, error) {
|
||||
switch order.Status {
|
||||
case StatusInvalid:
|
||||
// "invalid": The certificate will not be issued. Consider this
|
||||
// order process abandoned.
|
||||
return true, fmt.Errorf("final order is invalid: %w", order.Error)
|
||||
|
||||
case StatusPending:
|
||||
// "pending": The server does not believe that the client has
|
||||
// fulfilled the requirements. Check the "authorizations" array for
|
||||
// entries that are still pending.
|
||||
return true, fmt.Errorf("order pending, authorizations remaining: %v", order.Authorizations)
|
||||
|
||||
case StatusReady:
|
||||
// "ready": The server agrees that the requirements have been
|
||||
// fulfilled, and is awaiting finalization. Submit a finalization
|
||||
// request.
|
||||
// (we did just submit a finalization request, so this is an error)
|
||||
return true, fmt.Errorf("unexpected state: %s - order already finalized", order.Status)
|
||||
|
||||
case StatusProcessing:
|
||||
// "processing": The certificate is being issued. Send a GET request
|
||||
// after the time given in the "Retry-After" header field of the
|
||||
// response, if any.
|
||||
return false, nil
|
||||
|
||||
case StatusValid:
|
||||
// "valid": The server has issued the certificate and provisioned its
|
||||
// URL to the "certificate" field of the order. Download the
|
||||
// certificate.
|
||||
return true, nil
|
||||
|
||||
default:
|
||||
return true, fmt.Errorf("unrecognized order status: %s", order.Status)
|
||||
}
|
||||
}
|
136
vendor/github.com/mholt/acmez/acme/problem.go
generated
vendored
Normal file
136
vendor/github.com/mholt/acmez/acme/problem.go
generated
vendored
Normal file
@ -0,0 +1,136 @@
|
||||
// Copyright 2020 Matthew Holt
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package acme
|
||||
|
||||
import "fmt"
|
||||
|
||||
// Problem carries the details of an error from HTTP APIs as
|
||||
// defined in RFC 7807: https://tools.ietf.org/html/rfc7807
|
||||
// and as extended by RFC 8555 §6.7:
|
||||
// https://tools.ietf.org/html/rfc8555#section-6.7
|
||||
type Problem struct {
|
||||
// "type" (string) - A URI reference [RFC3986] that identifies the
|
||||
// problem type. This specification encourages that, when
|
||||
// dereferenced, it provide human-readable documentation for the
|
||||
// problem type (e.g., using HTML [W3C.REC-html5-20141028]). When
|
||||
// this member is not present, its value is assumed to be
|
||||
// "about:blank". §3.1
|
||||
Type string `json:"type"`
|
||||
|
||||
// "title" (string) - A short, human-readable summary of the problem
|
||||
// type. It SHOULD NOT change from occurrence to occurrence of the
|
||||
// problem, except for purposes of localization (e.g., using
|
||||
// proactive content negotiation; see [RFC7231], Section 3.4). §3.1
|
||||
Title string `json:"title,omitempty"`
|
||||
|
||||
// "status" (number) - The HTTP status code ([RFC7231], Section 6)
|
||||
// generated by the origin server for this occurrence of the problem.
|
||||
// §3.1
|
||||
Status int `json:"status,omitempty"`
|
||||
|
||||
// "detail" (string) - A human-readable explanation specific to this
|
||||
// occurrence of the problem. §3.1
|
||||
//
|
||||
// RFC 8555 §6.7: "Clients SHOULD display the 'detail' field of all
|
||||
// errors."
|
||||
Detail string `json:"detail,omitempty"`
|
||||
|
||||
// "instance" (string) - A URI reference that identifies the specific
|
||||
// occurrence of the problem. It may or may not yield further
|
||||
// information if dereferenced. §3.1
|
||||
Instance string `json:"instance,omitempty"`
|
||||
|
||||
// "Sometimes a CA may need to return multiple errors in response to a
|
||||
// request. Additionally, the CA may need to attribute errors to
|
||||
// specific identifiers. For instance, a newOrder request may contain
|
||||
// multiple identifiers for which the CA cannot issue certificates. In
|
||||
// this situation, an ACME problem document MAY contain the
|
||||
// 'subproblems' field, containing a JSON array of problem documents."
|
||||
// RFC 8555 §6.7.1
|
||||
Subproblems []Subproblem `json:"subproblems,omitempty"`
|
||||
|
||||
// For convenience, we've added this field to associate with a value
|
||||
// that is related to or caused the problem. It is not part of the
|
||||
// spec, but, if a challenge fails for example, we can associate the
|
||||
// error with the problematic authz object by setting this field.
|
||||
// Challenge failures will have this set to an Authorization type.
|
||||
Resource interface{} `json:"-"`
|
||||
}
|
||||
|
||||
func (p Problem) Error() string {
|
||||
// TODO: 7.3.3: Handle changes to Terms of Service (notice it uses the Instance field and Link header)
|
||||
|
||||
// RFC 8555 §6.7: "Clients SHOULD display the 'detail' field of all errors."
|
||||
s := fmt.Sprintf("HTTP %d %s - %s", p.Status, p.Type, p.Detail)
|
||||
if len(p.Subproblems) > 0 {
|
||||
for _, v := range p.Subproblems {
|
||||
s += fmt.Sprintf(", problem %q: %s", v.Type, v.Detail)
|
||||
}
|
||||
}
|
||||
if p.Instance != "" {
|
||||
s += ", url: " + p.Instance
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// Subproblem describes a more specific error in a problem according to
|
||||
// RFC 8555 §6.7.1: "An ACME problem document MAY contain the
|
||||
// 'subproblems' field, containing a JSON array of problem documents,
|
||||
// each of which MAY contain an 'identifier' field."
|
||||
type Subproblem struct {
|
||||
Problem
|
||||
|
||||
// "If present, the 'identifier' field MUST contain an ACME
|
||||
// identifier (Section 9.7.7)." §6.7.1
|
||||
Identifier Identifier `json:"identifier,omitempty"`
|
||||
}
|
||||
|
||||
// Standard token values for the "type" field of problems, as defined
|
||||
// in RFC 8555 §6.7: https://tools.ietf.org/html/rfc8555#section-6.7
|
||||
//
|
||||
// "To facilitate automatic response to errors, this document defines the
|
||||
// following standard tokens for use in the 'type' field (within the
|
||||
// ACME URN namespace 'urn:ietf:params:acme:error:') ... This list is not
|
||||
// exhaustive. The server MAY return errors whose 'type' field is set to
|
||||
// a URI other than those defined above."
|
||||
const (
|
||||
// The ACME error URN prefix.
|
||||
ProblemTypeNamespace = "urn:ietf:params:acme:error:"
|
||||
|
||||
ProblemTypeAccountDoesNotExist = ProblemTypeNamespace + "accountDoesNotExist"
|
||||
ProblemTypeAlreadyRevoked = ProblemTypeNamespace + "alreadyRevoked"
|
||||
ProblemTypeBadCSR = ProblemTypeNamespace + "badCSR"
|
||||
ProblemTypeBadNonce = ProblemTypeNamespace + "badNonce"
|
||||
ProblemTypeBadPublicKey = ProblemTypeNamespace + "badPublicKey"
|
||||
ProblemTypeBadRevocationReason = ProblemTypeNamespace + "badRevocationReason"
|
||||
ProblemTypeBadSignatureAlgorithm = ProblemTypeNamespace + "badSignatureAlgorithm"
|
||||
ProblemTypeCAA = ProblemTypeNamespace + "caa"
|
||||
ProblemTypeCompound = ProblemTypeNamespace + "compound"
|
||||
ProblemTypeConnection = ProblemTypeNamespace + "connection"
|
||||
ProblemTypeDNS = ProblemTypeNamespace + "dns"
|
||||
ProblemTypeExternalAccountRequired = ProblemTypeNamespace + "externalAccountRequired"
|
||||
ProblemTypeIncorrectResponse = ProblemTypeNamespace + "incorrectResponse"
|
||||
ProblemTypeInvalidContact = ProblemTypeNamespace + "invalidContact"
|
||||
ProblemTypeMalformed = ProblemTypeNamespace + "malformed"
|
||||
ProblemTypeOrderNotReady = ProblemTypeNamespace + "orderNotReady"
|
||||
ProblemTypeRateLimited = ProblemTypeNamespace + "rateLimited"
|
||||
ProblemTypeRejectedIdentifier = ProblemTypeNamespace + "rejectedIdentifier"
|
||||
ProblemTypeServerInternal = ProblemTypeNamespace + "serverInternal"
|
||||
ProblemTypeTLS = ProblemTypeNamespace + "tls"
|
||||
ProblemTypeUnauthorized = ProblemTypeNamespace + "unauthorized"
|
||||
ProblemTypeUnsupportedContact = ProblemTypeNamespace + "unsupportedContact"
|
||||
ProblemTypeUnsupportedIdentifier = ProblemTypeNamespace + "unsupportedIdentifier"
|
||||
ProblemTypeUserActionRequired = ProblemTypeNamespace + "userActionRequired"
|
||||
)
|
656
vendor/github.com/mholt/acmez/client.go
generated
vendored
Normal file
656
vendor/github.com/mholt/acmez/client.go
generated
vendored
Normal file
@ -0,0 +1,656 @@
|
||||
// Copyright 2020 Matthew Holt
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Package acmez implements the higher-level flow of the ACME specification,
|
||||
// RFC 8555: https://tools.ietf.org/html/rfc8555, specifically the sequence
|
||||
// in Section 7.1 (page 21).
|
||||
//
|
||||
// It makes it easy to obtain certificates with various challenge types
|
||||
// using pluggable challenge solvers, and provides some handy utilities for
|
||||
// implementing solvers and using the certificates. It DOES NOT manage
|
||||
// certificates, it only gets them from the ACME server.
|
||||
//
|
||||
// NOTE: This package's main function is to get a certificate, not manage it.
|
||||
// Most users will want to *manage* certificates over the lifetime of a
|
||||
// long-running program such as a HTTPS or TLS server, and should use CertMagic
|
||||
// instead: https://github.com/caddyserver/certmagic.
|
||||
package acmez
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto"
|
||||
"crypto/rand"
|
||||
"crypto/x509"
|
||||
"errors"
|
||||
"fmt"
|
||||
weakrand "math/rand"
|
||||
"net"
|
||||
"net/url"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/mholt/acmez/acme"
|
||||
"go.uber.org/zap"
|
||||
"golang.org/x/net/idna"
|
||||
)
|
||||
|
||||
func init() {
|
||||
weakrand.Seed(time.Now().UnixNano())
|
||||
}
|
||||
|
||||
// Client is a high-level API for ACME operations. It wraps
|
||||
// a lower-level ACME client with useful functions to make
|
||||
// common flows easier, especially for the issuance of
|
||||
// certificates.
|
||||
type Client struct {
|
||||
*acme.Client
|
||||
|
||||
// Map of solvers keyed by name of the challenge type.
|
||||
ChallengeSolvers map[string]Solver
|
||||
|
||||
// An optional logger. Default: no logs
|
||||
Logger *zap.Logger
|
||||
}
|
||||
|
||||
// ObtainCertificateUsingCSR obtains all resulting certificate chains using the given CSR, which
|
||||
// must be completely and properly filled out (particularly its DNSNames and Raw fields - this
|
||||
// usually involves creating a template CSR, then calling x509.CreateCertificateRequest, then
|
||||
// x509.ParseCertificateRequest on the output). The Subject CommonName is NOT considered.
|
||||
//
|
||||
// It implements every single part of the ACME flow described in RFC 8555 §7.1 with the exception
|
||||
// of "Create account" because this method signature does not have a way to return the udpated
|
||||
// account object. The account's status MUST be "valid" in order to succeed.
|
||||
//
|
||||
// As far as SANs go, this method currently only supports DNSNames on the csr.
|
||||
func (c *Client) ObtainCertificateUsingCSR(ctx context.Context, account acme.Account, csr *x509.CertificateRequest) ([]acme.Certificate, error) {
|
||||
if account.Status != acme.StatusValid {
|
||||
return nil, fmt.Errorf("account status is not valid: %s", account.Status)
|
||||
}
|
||||
if csr == nil {
|
||||
return nil, fmt.Errorf("missing CSR")
|
||||
}
|
||||
|
||||
var ids []acme.Identifier
|
||||
for _, name := range csr.DNSNames {
|
||||
// "The domain name MUST be encoded in the form in which it would appear
|
||||
// in a certificate. That is, it MUST be encoded according to the rules
|
||||
// in Section 7 of [RFC5280]." §7.1.4
|
||||
normalizedName, err := idna.ToASCII(name)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("converting identifier '%s' to ASCII: %v", name, err)
|
||||
}
|
||||
|
||||
ids = append(ids, acme.Identifier{
|
||||
Type: "dns",
|
||||
Value: normalizedName,
|
||||
})
|
||||
}
|
||||
if len(ids) == 0 {
|
||||
return nil, fmt.Errorf("no identifiers found")
|
||||
}
|
||||
|
||||
order := acme.Order{Identifiers: ids}
|
||||
var err error
|
||||
|
||||
// remember which challenge types failed for which identifiers
|
||||
// so we can retry with other challenge types
|
||||
failedChallengeTypes := make(failedChallengeMap)
|
||||
|
||||
const maxAttempts = 3 // hard cap on number of retries for good measure
|
||||
for attempt := 1; attempt <= maxAttempts; attempt++ {
|
||||
if attempt > 1 {
|
||||
select {
|
||||
case <-time.After(1 * time.Second):
|
||||
case <-ctx.Done():
|
||||
return nil, ctx.Err()
|
||||
}
|
||||
}
|
||||
|
||||
// create order for a new certificate
|
||||
order, err = c.Client.NewOrder(ctx, account, order)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("creating new order: %w", err)
|
||||
}
|
||||
|
||||
// solve one challenge for each authz on the order
|
||||
err = c.solveChallenges(ctx, account, order, failedChallengeTypes)
|
||||
|
||||
// yay, we win!
|
||||
if err == nil {
|
||||
break
|
||||
}
|
||||
|
||||
// for some errors, we can retry with different challenge types
|
||||
var problem acme.Problem
|
||||
if errors.As(err, &problem) {
|
||||
authz := problem.Resource.(acme.Authorization)
|
||||
if c.Logger != nil {
|
||||
c.Logger.Error("validating authorization",
|
||||
zap.String("identifier", authz.IdentifierValue()),
|
||||
zap.Error(err),
|
||||
zap.String("order", order.Location),
|
||||
zap.Int("attempt", attempt),
|
||||
zap.Int("max_attempts", maxAttempts))
|
||||
}
|
||||
err = fmt.Errorf("solving challenge: %s: %w", authz.IdentifierValue(), err)
|
||||
if errors.As(err, &retryableErr{}) {
|
||||
continue
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("solving challenges: %w (order=%s)", err, order.Location)
|
||||
}
|
||||
|
||||
if c.Logger != nil {
|
||||
c.Logger.Info("validations succeeded; finalizing order", zap.String("order", order.Location))
|
||||
}
|
||||
|
||||
// finalize the order, which requests the CA to issue us a certificate
|
||||
order, err = c.Client.FinalizeOrder(ctx, account, order, csr.Raw)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("finalizing order %s: %w", order.Location, err)
|
||||
}
|
||||
|
||||
// finally, download the certificate
|
||||
certChains, err := c.Client.GetCertificateChain(ctx, account, order.Certificate)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("downloading certificate chain from %s: %w (order=%s)",
|
||||
order.Certificate, err, order.Location)
|
||||
}
|
||||
|
||||
if c.Logger != nil {
|
||||
if len(certChains) == 0 {
|
||||
c.Logger.Info("no certificate chains offered by server")
|
||||
} else {
|
||||
c.Logger.Info("successfully downloaded available certificate chains",
|
||||
zap.Int("count", len(certChains)),
|
||||
zap.String("first_url", certChains[0].URL))
|
||||
}
|
||||
}
|
||||
|
||||
return certChains, nil
|
||||
}
|
||||
|
||||
// ObtainCertificate is the same as ObtainCertificateUsingCSR, except it is a slight wrapper
|
||||
// that generates the CSR for you. Doing so requires the private key you will be using for
|
||||
// the certificate (different from the account private key). It obtains a certificate for
|
||||
// the given SANs (domain names) using the provided account.
|
||||
func (c *Client) ObtainCertificate(ctx context.Context, account acme.Account, certPrivateKey crypto.Signer, sans []string) ([]acme.Certificate, error) {
|
||||
if len(sans) == 0 {
|
||||
return nil, fmt.Errorf("no DNS names provided: %v", sans)
|
||||
}
|
||||
if certPrivateKey == nil {
|
||||
return nil, fmt.Errorf("missing certificate private key")
|
||||
}
|
||||
|
||||
csrTemplate := new(x509.CertificateRequest)
|
||||
for _, name := range sans {
|
||||
if ip := net.ParseIP(name); ip != nil {
|
||||
csrTemplate.IPAddresses = append(csrTemplate.IPAddresses, ip)
|
||||
} else if strings.Contains(name, "@") {
|
||||
csrTemplate.EmailAddresses = append(csrTemplate.EmailAddresses, name)
|
||||
} else if u, err := url.Parse(name); err == nil && strings.Contains(name, "/") {
|
||||
csrTemplate.URIs = append(csrTemplate.URIs, u)
|
||||
} else {
|
||||
csrTemplate.DNSNames = append(csrTemplate.DNSNames, name)
|
||||
}
|
||||
}
|
||||
|
||||
// to properly fill out the CSR, we need to create it, then parse it
|
||||
csrDER, err := x509.CreateCertificateRequest(rand.Reader, csrTemplate, certPrivateKey)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("generating CSR: %v", err)
|
||||
}
|
||||
csr, err := x509.ParseCertificateRequest(csrDER)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("parsing generated CSR: %v", err)
|
||||
}
|
||||
|
||||
return c.ObtainCertificateUsingCSR(ctx, account, csr)
|
||||
}
|
||||
|
||||
// getAuthzObjects constructs stateful authorization objects for each authz on the order.
|
||||
// It includes all authorizations regardless of their status so that they can be
|
||||
// deactivated at the end if necessary. Be sure to check authz status before operating
|
||||
// on the authz; not all will be "pending" - some authorizations might already be valid.
|
||||
func (c *Client) getAuthzObjects(ctx context.Context, account acme.Account, order acme.Order,
|
||||
failedChallengeTypes failedChallengeMap) ([]*authzState, error) {
|
||||
var authzStates []*authzState
|
||||
var err error
|
||||
|
||||
// start by allowing each authz's solver to present for its challenge
|
||||
for _, authzURL := range order.Authorizations {
|
||||
authz := &authzState{account: account}
|
||||
authz.Authorization, err = c.Client.GetAuthorization(ctx, account, authzURL)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("getting authorization at %s: %w", authzURL, err)
|
||||
}
|
||||
|
||||
// add all offered challenge types to our memory if they
|
||||
// arent't there already; we use this for statistics to
|
||||
// choose the most successful challenge type over time;
|
||||
// if initial fill, randomize challenge order
|
||||
preferredChallengesMu.Lock()
|
||||
preferredWasEmpty := len(preferredChallenges) == 0
|
||||
for _, chal := range authz.Challenges {
|
||||
preferredChallenges.addUnique(chal.Type)
|
||||
}
|
||||
if preferredWasEmpty {
|
||||
weakrand.Shuffle(len(preferredChallenges), func(i, j int) {
|
||||
preferredChallenges[i], preferredChallenges[j] =
|
||||
preferredChallenges[j], preferredChallenges[i]
|
||||
})
|
||||
}
|
||||
preferredChallengesMu.Unlock()
|
||||
|
||||
// copy over any challenges that are not known to have already
|
||||
// failed, making them candidates for solving for this authz
|
||||
failedChallengeTypes.enqueueUnfailedChallenges(authz)
|
||||
|
||||
authzStates = append(authzStates, authz)
|
||||
}
|
||||
|
||||
// sort authzs so that challenges which require waiting go first; no point
|
||||
// in getting authorizations quickly while others will take a long time
|
||||
sort.SliceStable(authzStates, func(i, j int) bool {
|
||||
_, iIsWaiter := authzStates[i].currentSolver.(Waiter)
|
||||
_, jIsWaiter := authzStates[j].currentSolver.(Waiter)
|
||||
// "if i is a waiter, and j is not a waiter, then i is less than j"
|
||||
return iIsWaiter && !jIsWaiter
|
||||
})
|
||||
|
||||
return authzStates, nil
|
||||
}
|
||||
|
||||
func (c *Client) solveChallenges(ctx context.Context, account acme.Account, order acme.Order, failedChallengeTypes failedChallengeMap) error {
|
||||
authzStates, err := c.getAuthzObjects(ctx, account, order, failedChallengeTypes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// when the function returns, make sure we clean up any and all resources
|
||||
defer func() {
|
||||
// always clean up any remaining challenge solvers
|
||||
for _, authz := range authzStates {
|
||||
if authz.currentSolver == nil {
|
||||
// happens when authz state ended on a challenge we have no
|
||||
// solver for or if we have already cleaned up this solver
|
||||
continue
|
||||
}
|
||||
if err := authz.currentSolver.CleanUp(ctx, authz.currentChallenge); err != nil {
|
||||
if c.Logger != nil {
|
||||
c.Logger.Error("cleaning up solver",
|
||||
zap.String("identifier", authz.IdentifierValue()),
|
||||
zap.String("challenge_type", authz.currentChallenge.Type),
|
||||
zap.Error(err))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if err == nil {
|
||||
return
|
||||
}
|
||||
|
||||
// if this function returns with an error, make sure to deactivate
|
||||
// all pending or valid authorization objects so they don't "leak"
|
||||
// See: https://github.com/go-acme/lego/issues/383 and https://github.com/go-acme/lego/issues/353
|
||||
for _, authz := range authzStates {
|
||||
if authz.Status != acme.StatusPending && authz.Status != acme.StatusValid {
|
||||
continue
|
||||
}
|
||||
updatedAuthz, err := c.Client.DeactivateAuthorization(ctx, account, authz.Location)
|
||||
if err != nil {
|
||||
if c.Logger != nil {
|
||||
c.Logger.Error("deactivating authorization",
|
||||
zap.String("identifier", authz.IdentifierValue()),
|
||||
zap.String("authz", authz.Location),
|
||||
zap.Error(err))
|
||||
}
|
||||
}
|
||||
authz.Authorization = updatedAuthz
|
||||
}
|
||||
}()
|
||||
|
||||
// present for all challenges first; this allows them all to begin any
|
||||
// slow tasks up front if necessary before we start polling/waiting
|
||||
for _, authz := range authzStates {
|
||||
// see §7.1.6 for state transitions
|
||||
if authz.Status != acme.StatusPending && authz.Status != acme.StatusValid {
|
||||
return fmt.Errorf("authz %s has unexpected status; order will fail: %s", authz.Location, authz.Status)
|
||||
}
|
||||
if authz.Status == acme.StatusValid {
|
||||
continue
|
||||
}
|
||||
|
||||
err = c.presentForNextChallenge(ctx, authz)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// now that all solvers have had the opportunity to present, tell
|
||||
// the server to begin the selected challenge for each authz
|
||||
for _, authz := range authzStates {
|
||||
err = c.initiateCurrentChallenge(ctx, authz)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// poll each authz to wait for completion of all challenges
|
||||
for _, authz := range authzStates {
|
||||
err = c.pollAuthorization(ctx, account, authz, failedChallengeTypes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Client) presentForNextChallenge(ctx context.Context, authz *authzState) error {
|
||||
if authz.Status != acme.StatusPending {
|
||||
if authz.Status == acme.StatusValid && c.Logger != nil {
|
||||
c.Logger.Info("authorization already valid",
|
||||
zap.String("identifier", authz.IdentifierValue()),
|
||||
zap.String("authz_url", authz.Location),
|
||||
zap.Time("expires", authz.Expires))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
err := c.nextChallenge(authz)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if c.Logger != nil {
|
||||
c.Logger.Info("trying to solve challenge",
|
||||
zap.String("identifier", authz.IdentifierValue()),
|
||||
zap.String("challenge_type", authz.currentChallenge.Type),
|
||||
zap.String("ca", c.Directory))
|
||||
}
|
||||
|
||||
err = authz.currentSolver.Present(ctx, authz.currentChallenge)
|
||||
if err != nil {
|
||||
return fmt.Errorf("presenting for challenge: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Client) initiateCurrentChallenge(ctx context.Context, authz *authzState) error {
|
||||
if authz.Status != acme.StatusPending {
|
||||
return nil
|
||||
}
|
||||
|
||||
// by now, all challenges should have had an opportunity to present, so
|
||||
// if this solver needs more time to finish presenting, wait on it now
|
||||
// (yes, this does block the initiation of the other challenges, but
|
||||
// that's probably OK, since we can't finalize the order until the slow
|
||||
// challenges are done too)
|
||||
if waiter, ok := authz.currentSolver.(Waiter); ok {
|
||||
err := waiter.Wait(ctx, authz.currentChallenge)
|
||||
if err != nil {
|
||||
return fmt.Errorf("waiting for solver %T to be ready: %w", authz.currentSolver, err)
|
||||
}
|
||||
}
|
||||
|
||||
// tell the server to initiate the challenge
|
||||
var err error
|
||||
authz.currentChallenge, err = c.Client.InitiateChallenge(ctx, authz.account, authz.currentChallenge)
|
||||
if err != nil {
|
||||
return fmt.Errorf("initiating challenge with server: %w", err)
|
||||
}
|
||||
|
||||
if c.Logger != nil {
|
||||
c.Logger.Debug("challenge accepted",
|
||||
zap.String("identifier", authz.IdentifierValue()),
|
||||
zap.String("challenge_type", authz.currentChallenge.Type))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// nextChallenge sets the next challenge (and associated solver) on
|
||||
// authz; it returns an error if there is no compatible challenge.
|
||||
func (c *Client) nextChallenge(authz *authzState) error {
|
||||
preferredChallengesMu.Lock()
|
||||
defer preferredChallengesMu.Unlock()
|
||||
|
||||
// find the most-preferred challenge that is also in the list of
|
||||
// remaining challenges, then make sure we have a solver for it
|
||||
for _, prefChalType := range preferredChallenges {
|
||||
for i, remainingChal := range authz.remainingChallenges {
|
||||
if remainingChal.Type != prefChalType.typeName {
|
||||
continue
|
||||
}
|
||||
authz.currentChallenge = remainingChal
|
||||
authz.currentSolver = c.ChallengeSolvers[authz.currentChallenge.Type]
|
||||
if authz.currentSolver != nil {
|
||||
authz.remainingChallenges = append(authz.remainingChallenges[:i], authz.remainingChallenges[i+1:]...)
|
||||
return nil
|
||||
}
|
||||
if c.Logger != nil {
|
||||
c.Logger.Debug("no solver configured", zap.String("challenge_type", remainingChal.Type))
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
return fmt.Errorf("%s: no solvers available for remaining challenges (configured=%v offered=%v remaining=%v)",
|
||||
authz.IdentifierValue(), c.enabledChallengeTypes(), authz.listOfferedChallenges(), authz.listRemainingChallenges())
|
||||
}
|
||||
|
||||
func (c *Client) pollAuthorization(ctx context.Context, account acme.Account, authz *authzState, failedChallengeTypes failedChallengeMap) error {
|
||||
// In §7.5.1, the spec says:
|
||||
//
|
||||
// "For challenges where the client can tell when the server has
|
||||
// validated the challenge (e.g., by seeing an HTTP or DNS request
|
||||
// from the server), the client SHOULD NOT begin polling until it has
|
||||
// seen the validation request from the server."
|
||||
//
|
||||
// However, in practice, this is difficult in the general case because
|
||||
// we would need to design some relatively-nuanced concurrency and hope
|
||||
// that the solver implementations also get their side right -- and the
|
||||
// fact that it's even possible only sometimes makes it harder, because
|
||||
// each solver needs a way to signal whether we should wait for its
|
||||
// approval. So no, I've decided not to implement that recommendation
|
||||
// in this particular library, but any implementations that use the lower
|
||||
// ACME API directly are welcome and encouraged to do so where possible.
|
||||
var err error
|
||||
authz.Authorization, err = c.Client.PollAuthorization(ctx, account, authz.Authorization)
|
||||
|
||||
// if a challenge was attempted (i.e. did not start valid)...
|
||||
if authz.currentSolver != nil {
|
||||
// increment the statistics on this challenge type before handling error
|
||||
preferredChallengesMu.Lock()
|
||||
preferredChallenges.increment(authz.currentChallenge.Type, err == nil)
|
||||
preferredChallengesMu.Unlock()
|
||||
|
||||
// always clean up the challenge solver after polling, regardless of error
|
||||
cleanupErr := authz.currentSolver.CleanUp(ctx, authz.currentChallenge)
|
||||
if cleanupErr != nil && c.Logger != nil {
|
||||
c.Logger.Error("cleaning up solver",
|
||||
zap.String("identifier", authz.IdentifierValue()),
|
||||
zap.String("challenge_type", authz.currentChallenge.Type),
|
||||
zap.Error(err))
|
||||
}
|
||||
authz.currentSolver = nil // avoid cleaning it up again later
|
||||
}
|
||||
|
||||
// finally, handle any error from validating the authz
|
||||
if err != nil {
|
||||
var problem acme.Problem
|
||||
if errors.As(err, &problem) {
|
||||
if c.Logger != nil {
|
||||
c.Logger.Error("challenge failed",
|
||||
zap.String("identifier", authz.IdentifierValue()),
|
||||
zap.String("challenge_type", authz.currentChallenge.Type),
|
||||
zap.Int("status_code", problem.Status),
|
||||
zap.String("problem_type", problem.Type),
|
||||
zap.String("error", problem.Detail))
|
||||
}
|
||||
|
||||
failedChallengeTypes.rememberFailedChallenge(authz)
|
||||
|
||||
switch problem.Type {
|
||||
case acme.ProblemTypeConnection,
|
||||
acme.ProblemTypeDNS,
|
||||
acme.ProblemTypeServerInternal,
|
||||
acme.ProblemTypeUnauthorized,
|
||||
acme.ProblemTypeTLS:
|
||||
// this error might be recoverable with another challenge type
|
||||
return retryableErr{err}
|
||||
}
|
||||
}
|
||||
return fmt.Errorf("[%s] %w", authz.Authorization.IdentifierValue(), err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Client) enabledChallengeTypes() []string {
|
||||
enabledChallenges := make([]string, 0, len(c.ChallengeSolvers))
|
||||
for name, val := range c.ChallengeSolvers {
|
||||
if val != nil {
|
||||
enabledChallenges = append(enabledChallenges, name)
|
||||
}
|
||||
}
|
||||
return enabledChallenges
|
||||
}
|
||||
|
||||
type authzState struct {
|
||||
acme.Authorization
|
||||
account acme.Account
|
||||
currentChallenge acme.Challenge
|
||||
currentSolver Solver
|
||||
remainingChallenges []acme.Challenge
|
||||
}
|
||||
|
||||
func (authz authzState) listOfferedChallenges() []string {
|
||||
return challengeTypeNames(authz.Challenges)
|
||||
}
|
||||
|
||||
func (authz authzState) listRemainingChallenges() []string {
|
||||
return challengeTypeNames(authz.remainingChallenges)
|
||||
}
|
||||
|
||||
func challengeTypeNames(challengeList []acme.Challenge) []string {
|
||||
names := make([]string, 0, len(challengeList))
|
||||
for _, chal := range challengeList {
|
||||
names = append(names, chal.Type)
|
||||
}
|
||||
return names
|
||||
}
|
||||
|
||||
// TODO: possibly configurable policy? converge to most successful (current) vs. completely random
|
||||
|
||||
// challengeHistory is a memory of how successful a challenge type is.
|
||||
type challengeHistory struct {
|
||||
typeName string
|
||||
successes, total int
|
||||
}
|
||||
|
||||
func (ch challengeHistory) successRatio() float64 {
|
||||
if ch.total == 0 {
|
||||
return 1.0
|
||||
}
|
||||
return float64(ch.successes) / float64(ch.total)
|
||||
}
|
||||
|
||||
// failedChallengeMap keeps track of failed challenge types per identifier.
|
||||
type failedChallengeMap map[string][]string
|
||||
|
||||
func (fcm failedChallengeMap) rememberFailedChallenge(authz *authzState) {
|
||||
idKey := fcm.idKey(authz)
|
||||
fcm[idKey] = append(fcm[idKey], authz.currentChallenge.Type)
|
||||
}
|
||||
|
||||
// enqueueUnfailedChallenges enqueues each challenge offered in authz if it
|
||||
// is not known to have failed for the authz's identifier already.
|
||||
func (fcm failedChallengeMap) enqueueUnfailedChallenges(authz *authzState) {
|
||||
idKey := fcm.idKey(authz)
|
||||
for _, chal := range authz.Challenges {
|
||||
if !contains(fcm[idKey], chal.Type) {
|
||||
authz.remainingChallenges = append(authz.remainingChallenges, chal)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (fcm failedChallengeMap) idKey(authz *authzState) string {
|
||||
return authz.Identifier.Type + authz.IdentifierValue()
|
||||
}
|
||||
|
||||
// challengeTypes is a list of challenges we've seen and/or
|
||||
// used previously. It sorts from most successful to least
|
||||
// successful, such that most successful challenges are first.
|
||||
type challengeTypes []challengeHistory
|
||||
|
||||
// Len is part of sort.Interface.
|
||||
func (ct challengeTypes) Len() int { return len(ct) }
|
||||
|
||||
// Swap is part of sort.Interface.
|
||||
func (ct challengeTypes) Swap(i, j int) { ct[i], ct[j] = ct[j], ct[i] }
|
||||
|
||||
// Less is part of sort.Interface. It sorts challenge
|
||||
// types from highest success ratio to lowest.
|
||||
func (ct challengeTypes) Less(i, j int) bool {
|
||||
return ct[i].successRatio() > ct[j].successRatio()
|
||||
}
|
||||
|
||||
func (ct *challengeTypes) addUnique(challengeType string) {
|
||||
for _, c := range *ct {
|
||||
if c.typeName == challengeType {
|
||||
return
|
||||
}
|
||||
}
|
||||
*ct = append(*ct, challengeHistory{typeName: challengeType})
|
||||
}
|
||||
|
||||
func (ct challengeTypes) increment(challengeType string, successful bool) {
|
||||
defer sort.Stable(ct) // keep most successful challenges in front
|
||||
for i, c := range ct {
|
||||
if c.typeName == challengeType {
|
||||
ct[i].total++
|
||||
if successful {
|
||||
ct[i].successes++
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func contains(haystack []string, needle string) bool {
|
||||
for _, s := range haystack {
|
||||
if s == needle {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// retryableErr wraps an error that indicates the caller should retry
|
||||
// the operation; specifically with a different challenge type.
|
||||
type retryableErr struct{ error }
|
||||
|
||||
func (re retryableErr) Unwrap() error { return re.error }
|
||||
|
||||
// Keep a list of challenges we've seen offered by servers,
|
||||
// and prefer keep an ordered list of
|
||||
var (
|
||||
preferredChallenges challengeTypes
|
||||
preferredChallengesMu sync.Mutex
|
||||
)
|
8
vendor/github.com/mholt/acmez/go.mod
generated
vendored
Normal file
8
vendor/github.com/mholt/acmez/go.mod
generated
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
module github.com/mholt/acmez
|
||||
|
||||
go 1.14
|
||||
|
||||
require (
|
||||
go.uber.org/zap v1.15.0
|
||||
golang.org/x/net v0.0.0-20200707034311-ab3426394381
|
||||
)
|
61
vendor/github.com/mholt/acmez/go.sum
generated
vendored
Normal file
61
vendor/github.com/mholt/acmez/go.sum
generated
vendored
Normal file
@ -0,0 +1,61 @@
|
||||
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
go.uber.org/atomic v1.6.0 h1:Ezj3JGmsOnG1MoRWQkPBsKLe9DwWD9QeXzTRzzldNVk=
|
||||
go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
|
||||
go.uber.org/multierr v1.5.0 h1:KCa4XfM8CWFCpxXRGok+Q0SS/0XBhMDbHHGABQLvD2A=
|
||||
go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU=
|
||||
go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee h1:0mgffUl7nfd+FpvXMVz4IDEaUSmT1ysygQC7qYo7sG4=
|
||||
go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
|
||||
go.uber.org/zap v1.15.0 h1:ZZCA22JRF2gQE5FoNmhmrf7jeJJ2uhqDUNRYKm8dvmM=
|
||||
go.uber.org/zap v1.15.0/go.mod h1:Mb2vm2krFEG5DV0W9qcHBYFtp/Wku1cvYaqPsS/WYfc=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/lint v0.0.0-20190930215403-16217165b5de h1:5hukYrvBGR8/eNkX5mdUezrA6JiaEZDtJb9Ei+1LlBs=
|
||||
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859 h1:R/3boaszxrf1GEUWTVDzSKVwLmSJpwZ1yqXm8j0v2QI=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200707034311-ab3426394381 h1:VXak5I6aEWmAXeQjA+QSZzlgNrpq9mjcfDemuexIKsU=
|
||||
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5 h1:hKsoRgsbwY1NafxrwTs+k64bikrLBkAgPir1TNCj3Zs=
|
||||
golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
honnef.co/go/tools v0.0.1-2019.2.3 h1:3JgtbtFHMiCmsznwGVTUWbgGov+pVqnlf1dEJTNAXeM=
|
||||
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
72
vendor/github.com/mholt/acmez/solver.go
generated
vendored
Normal file
72
vendor/github.com/mholt/acmez/solver.go
generated
vendored
Normal file
@ -0,0 +1,72 @@
|
||||
// Copyright 2020 Matthew Holt
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package acmez
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/mholt/acmez/acme"
|
||||
)
|
||||
|
||||
// Solver is a type that can solve ACME challenges. All
|
||||
// implementations MUST honor context cancellation.
|
||||
type Solver interface {
|
||||
// Present is called just before a challenge is initiated.
|
||||
// The implementation MUST prepare anything that is necessary
|
||||
// for completing the challenge; for example, provisioning
|
||||
// an HTTP resource, TLS certificate, or a DNS record.
|
||||
//
|
||||
// It MUST return quickly. If presenting the challenge token
|
||||
// will take time, then the implementation MUST do the
|
||||
// minimum amount of work required in this method, and
|
||||
// SHOULD additionally implement the Waiter interface.
|
||||
// For example, a DNS challenge solver might make a quick
|
||||
// HTTP request to a provider's API to create a new DNS
|
||||
// record, but it might be several minutes or hours before
|
||||
// the DNS record propagates. The API request should be
|
||||
// done in Present(), and waiting for propagation should
|
||||
// be done in Wait().
|
||||
Present(context.Context, acme.Challenge) error
|
||||
|
||||
// CleanUp is called after a challenge is finished, whether
|
||||
// successful or not. It MUST free/remove any resources it
|
||||
// allocated/created during Present. It SHOULD NOT require
|
||||
// that Present ran successfully. It MUST return quickly.
|
||||
CleanUp(context.Context, acme.Challenge) error
|
||||
}
|
||||
|
||||
// Waiter is an optional interface for Solvers to implement. Its
|
||||
// primary purpose is to help ensure the challenge can be solved
|
||||
// before the server gives up trying to verify the challenge.
|
||||
//
|
||||
// If implemented, it will be called after Present() but just
|
||||
// before the challenge is initiated with the server. It blocks
|
||||
// until the challenge is ready to be solved. (For example,
|
||||
// waiting on a DNS record to propagate.) This allows challenges
|
||||
// to succeed that would normally fail because they take too long
|
||||
// to set up (i.e. the ACME server would give up polling DNS or
|
||||
// the client would timeout its polling). By separating Present()
|
||||
// from Wait(), it allows the slow part of all solvers to begin
|
||||
// up front, rather than waiting on each solver one at a time.
|
||||
//
|
||||
// It MUST NOT do anything exclusive of Present() that is required
|
||||
// for the challenge to succeed. In other words, if Present() is
|
||||
// called but Wait() is not, then the challenge should still be able
|
||||
// to succeed assuming infinite time.
|
||||
//
|
||||
// Implementations MUST honor context cancellation.
|
||||
type Waiter interface {
|
||||
Wait(context.Context, acme.Challenge) error
|
||||
}
|
98
vendor/github.com/mholt/acmez/tlsalpn01.go
generated
vendored
Normal file
98
vendor/github.com/mholt/acmez/tlsalpn01.go
generated
vendored
Normal file
@ -0,0 +1,98 @@
|
||||
// Copyright 2020 Matthew Holt
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package acmez
|
||||
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
"crypto/rand"
|
||||
"crypto/sha256"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"encoding/asn1"
|
||||
"encoding/pem"
|
||||
"math/big"
|
||||
"time"
|
||||
|
||||
"github.com/mholt/acmez/acme"
|
||||
)
|
||||
|
||||
// TLSALPN01ChallengeCert creates a certificate that can be used for
|
||||
// handshakes while solving the tls-alpn-01 challenge. See RFC 8737 §3.
|
||||
func TLSALPN01ChallengeCert(challenge acme.Challenge) (*tls.Certificate, error) {
|
||||
keyAuthSum := sha256.Sum256([]byte(challenge.KeyAuthorization))
|
||||
keyAuthSumASN1, err := asn1.Marshal(keyAuthSum[:sha256.Size])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
certKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
challengeKeyASN1, err := x509.MarshalECPrivateKey(certKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
|
||||
serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
template := x509.Certificate{
|
||||
SerialNumber: serialNumber,
|
||||
Subject: pkix.Name{CommonName: "ACME challenge"},
|
||||
NotBefore: time.Now(),
|
||||
NotAfter: time.Now().Add(24 * time.Hour * 365),
|
||||
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
|
||||
BasicConstraintsValid: true,
|
||||
DNSNames: []string{challenge.Identifier.Value},
|
||||
|
||||
// add key authentication digest as the acmeValidation-v1 extension
|
||||
// (marked as critical such that it won't be used by non-ACME software).
|
||||
// Reference: https://www.rfc-editor.org/rfc/rfc8737.html#section-3
|
||||
ExtraExtensions: []pkix.Extension{
|
||||
{
|
||||
Id: idPEACMEIdentifierV1,
|
||||
Critical: true,
|
||||
Value: keyAuthSumASN1,
|
||||
},
|
||||
},
|
||||
}
|
||||
challengeCertDER, err := x509.CreateCertificate(rand.Reader, &template, &template, &certKey.PublicKey, certKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
challengeCertPEM := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: challengeCertDER})
|
||||
challengeKeyPEM := pem.EncodeToMemory(&pem.Block{Type: "EC PRIVATE KEY", Bytes: challengeKeyASN1})
|
||||
|
||||
cert, err := tls.X509KeyPair(challengeCertPEM, challengeKeyPEM)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &cert, nil
|
||||
}
|
||||
|
||||
// ACMETLS1Protocol is the ALPN value for the TLS-ALPN challenge
|
||||
// handshake. See RFC 8737 §6.2.
|
||||
const ACMETLS1Protocol = "acme-tls/1"
|
||||
|
||||
// idPEACMEIdentifierV1 is the SMI Security for PKIX Certification Extension OID referencing the ACME extension.
|
||||
// See RFC 8737 §6.1. https://www.rfc-editor.org/rfc/rfc8737.html#section-6.1
|
||||
var idPEACMEIdentifierV1 = asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 1, 31}
|
1
vendor/github.com/miekg/dns/README.md
generated
vendored
1
vendor/github.com/miekg/dns/README.md
generated
vendored
@ -28,6 +28,7 @@ A not-so-up-to-date-list-that-may-be-actually-current:
|
||||
* https://github.com/coredns/coredns
|
||||
* https://cloudflare.com
|
||||
* https://github.com/abh/geodns
|
||||
* https://github.com/baidu/bfe
|
||||
* http://www.statdns.com/
|
||||
* http://www.dnsinspect.com/
|
||||
* https://github.com/chuangbo/jianbing-dictionary-dns
|
||||
|
37
vendor/github.com/miekg/dns/client.go
generated
vendored
37
vendor/github.com/miekg/dns/client.go
generated
vendored
@ -124,15 +124,38 @@ func (c *Client) Dial(address string) (conn *Conn, err error) {
|
||||
// of 512 bytes
|
||||
// To specify a local address or a timeout, the caller has to set the `Client.Dialer`
|
||||
// attribute appropriately
|
||||
|
||||
func (c *Client) Exchange(m *Msg, address string) (r *Msg, rtt time.Duration, err error) {
|
||||
co, err := c.Dial(address)
|
||||
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
defer co.Close()
|
||||
return c.ExchangeWithConn(m, co)
|
||||
}
|
||||
|
||||
// ExchangeWithConn has the same behavior as Exchange, just with a predetermined connection
|
||||
// that will be used instead of creating a new one.
|
||||
// Usage pattern with a *dns.Client:
|
||||
// c := new(dns.Client)
|
||||
// // connection management logic goes here
|
||||
//
|
||||
// conn := c.Dial(address)
|
||||
// in, rtt, err := c.ExchangeWithConn(message, conn)
|
||||
//
|
||||
// This allows users of the library to implement their own connection management,
|
||||
// as opposed to Exchange, which will always use new connections and incur the added overhead
|
||||
// that entails when using "tcp" and especially "tcp-tls" clients.
|
||||
func (c *Client) ExchangeWithConn(m *Msg, conn *Conn) (r *Msg, rtt time.Duration, err error) {
|
||||
if !c.SingleInflight {
|
||||
return c.exchange(m, address)
|
||||
return c.exchange(m, conn)
|
||||
}
|
||||
|
||||
q := m.Question[0]
|
||||
key := fmt.Sprintf("%s:%d:%d", q.Name, q.Qtype, q.Qclass)
|
||||
r, rtt, err, shared := c.group.Do(key, func() (*Msg, time.Duration, error) {
|
||||
return c.exchange(m, address)
|
||||
return c.exchange(m, conn)
|
||||
})
|
||||
if r != nil && shared {
|
||||
r = r.Copy()
|
||||
@ -141,15 +164,7 @@ func (c *Client) Exchange(m *Msg, address string) (r *Msg, rtt time.Duration, er
|
||||
return r, rtt, err
|
||||
}
|
||||
|
||||
func (c *Client) exchange(m *Msg, a string) (r *Msg, rtt time.Duration, err error) {
|
||||
var co *Conn
|
||||
|
||||
co, err = c.Dial(a)
|
||||
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
defer co.Close()
|
||||
func (c *Client) exchange(m *Msg, co *Conn) (r *Msg, rtt time.Duration, err error) {
|
||||
|
||||
opt := m.IsEdns0()
|
||||
// If EDNS0 is used use that for size.
|
||||
|
10
vendor/github.com/miekg/dns/defaults.go
generated
vendored
10
vendor/github.com/miekg/dns/defaults.go
generated
vendored
@ -105,7 +105,7 @@ func (dns *Msg) SetAxfr(z string) *Msg {
|
||||
|
||||
// SetTsig appends a TSIG RR to the message.
|
||||
// This is only a skeleton TSIG RR that is added as the last RR in the
|
||||
// additional section. The Tsig is calculated when the message is being send.
|
||||
// additional section. The TSIG is calculated when the message is being send.
|
||||
func (dns *Msg) SetTsig(z, algo string, fudge uint16, timesigned int64) *Msg {
|
||||
t := new(TSIG)
|
||||
t.Hdr = RR_Header{z, TypeTSIG, ClassANY, 0, 0}
|
||||
@ -317,6 +317,12 @@ func Fqdn(s string) string {
|
||||
return s + "."
|
||||
}
|
||||
|
||||
// CanonicalName returns the domain name in canonical form. A name in canonical
|
||||
// form is lowercase and fully qualified. See Section 6.2 in RFC 4034.
|
||||
func CanonicalName(s string) string {
|
||||
return strings.ToLower(Fqdn(s))
|
||||
}
|
||||
|
||||
// Copied from the official Go code.
|
||||
|
||||
// ReverseAddr returns the in-addr.arpa. or ip6.arpa. hostname of the IP
|
||||
@ -364,7 +370,7 @@ func (t Type) String() string {
|
||||
// String returns the string representation for the class c.
|
||||
func (c Class) String() string {
|
||||
if s, ok := ClassToString[uint16(c)]; ok {
|
||||
// Only emit mnemonics when they are unambiguous, specically ANY is in both.
|
||||
// Only emit mnemonics when they are unambiguous, specially ANY is in both.
|
||||
if _, ok := StringToType[s]; !ok {
|
||||
return s
|
||||
}
|
||||
|
56
vendor/github.com/miekg/dns/dnssec.go
generated
vendored
56
vendor/github.com/miekg/dns/dnssec.go
generated
vendored
@ -200,7 +200,7 @@ func (k *DNSKEY) ToDS(h uint8) *DS {
|
||||
wire = wire[:n]
|
||||
|
||||
owner := make([]byte, 255)
|
||||
off, err1 := PackDomainName(strings.ToLower(k.Hdr.Name), owner, 0, nil, false)
|
||||
off, err1 := PackDomainName(CanonicalName(k.Hdr.Name), owner, 0, nil, false)
|
||||
if err1 != nil {
|
||||
return nil
|
||||
}
|
||||
@ -285,7 +285,7 @@ func (rr *RRSIG) Sign(k crypto.Signer, rrset []RR) error {
|
||||
sigwire.Inception = rr.Inception
|
||||
sigwire.KeyTag = rr.KeyTag
|
||||
// For signing, lowercase this name
|
||||
sigwire.SignerName = strings.ToLower(rr.SignerName)
|
||||
sigwire.SignerName = CanonicalName(rr.SignerName)
|
||||
|
||||
// Create the desired binary blob
|
||||
signdata := make([]byte, DefaultMsgSize)
|
||||
@ -423,7 +423,7 @@ func (rr *RRSIG) Verify(k *DNSKEY, rrset []RR) error {
|
||||
sigwire.Expiration = rr.Expiration
|
||||
sigwire.Inception = rr.Inception
|
||||
sigwire.KeyTag = rr.KeyTag
|
||||
sigwire.SignerName = strings.ToLower(rr.SignerName)
|
||||
sigwire.SignerName = CanonicalName(rr.SignerName)
|
||||
// Create the desired binary blob
|
||||
signeddata := make([]byte, DefaultMsgSize)
|
||||
n, err := packSigWire(sigwire, signeddata)
|
||||
@ -659,7 +659,7 @@ func rawSignatureData(rrset []RR, s *RRSIG) (buf []byte, err error) {
|
||||
h.Name = "*." + strings.Join(labels[len(labels)-int(s.Labels):], ".") + "."
|
||||
}
|
||||
// RFC 4034: 6.2. Canonical RR Form. (2) - domain name to lowercase
|
||||
h.Name = strings.ToLower(h.Name)
|
||||
h.Name = CanonicalName(h.Name)
|
||||
// 6.2. Canonical RR Form. (3) - domain rdata to lowercase.
|
||||
// NS, MD, MF, CNAME, SOA, MB, MG, MR, PTR,
|
||||
// HINFO, MINFO, MX, RP, AFSDB, RT, SIG, PX, NXT, NAPTR, KX,
|
||||
@ -672,49 +672,49 @@ func rawSignatureData(rrset []RR, s *RRSIG) (buf []byte, err error) {
|
||||
// conversion.
|
||||
switch x := r1.(type) {
|
||||
case *NS:
|
||||
x.Ns = strings.ToLower(x.Ns)
|
||||
x.Ns = CanonicalName(x.Ns)
|
||||
case *MD:
|
||||
x.Md = strings.ToLower(x.Md)
|
||||
x.Md = CanonicalName(x.Md)
|
||||
case *MF:
|
||||
x.Mf = strings.ToLower(x.Mf)
|
||||
x.Mf = CanonicalName(x.Mf)
|
||||
case *CNAME:
|
||||
x.Target = strings.ToLower(x.Target)
|
||||
x.Target = CanonicalName(x.Target)
|
||||
case *SOA:
|
||||
x.Ns = strings.ToLower(x.Ns)
|
||||
x.Mbox = strings.ToLower(x.Mbox)
|
||||
x.Ns = CanonicalName(x.Ns)
|
||||
x.Mbox = CanonicalName(x.Mbox)
|
||||
case *MB:
|
||||
x.Mb = strings.ToLower(x.Mb)
|
||||
x.Mb = CanonicalName(x.Mb)
|
||||
case *MG:
|
||||
x.Mg = strings.ToLower(x.Mg)
|
||||
x.Mg = CanonicalName(x.Mg)
|
||||
case *MR:
|
||||
x.Mr = strings.ToLower(x.Mr)
|
||||
x.Mr = CanonicalName(x.Mr)
|
||||
case *PTR:
|
||||
x.Ptr = strings.ToLower(x.Ptr)
|
||||
x.Ptr = CanonicalName(x.Ptr)
|
||||
case *MINFO:
|
||||
x.Rmail = strings.ToLower(x.Rmail)
|
||||
x.Email = strings.ToLower(x.Email)
|
||||
x.Rmail = CanonicalName(x.Rmail)
|
||||
x.Email = CanonicalName(x.Email)
|
||||
case *MX:
|
||||
x.Mx = strings.ToLower(x.Mx)
|
||||
x.Mx = CanonicalName(x.Mx)
|
||||
case *RP:
|
||||
x.Mbox = strings.ToLower(x.Mbox)
|
||||
x.Txt = strings.ToLower(x.Txt)
|
||||
x.Mbox = CanonicalName(x.Mbox)
|
||||
x.Txt = CanonicalName(x.Txt)
|
||||
case *AFSDB:
|
||||
x.Hostname = strings.ToLower(x.Hostname)
|
||||
x.Hostname = CanonicalName(x.Hostname)
|
||||
case *RT:
|
||||
x.Host = strings.ToLower(x.Host)
|
||||
x.Host = CanonicalName(x.Host)
|
||||
case *SIG:
|
||||
x.SignerName = strings.ToLower(x.SignerName)
|
||||
x.SignerName = CanonicalName(x.SignerName)
|
||||
case *PX:
|
||||
x.Map822 = strings.ToLower(x.Map822)
|
||||
x.Mapx400 = strings.ToLower(x.Mapx400)
|
||||
x.Map822 = CanonicalName(x.Map822)
|
||||
x.Mapx400 = CanonicalName(x.Mapx400)
|
||||
case *NAPTR:
|
||||
x.Replacement = strings.ToLower(x.Replacement)
|
||||
x.Replacement = CanonicalName(x.Replacement)
|
||||
case *KX:
|
||||
x.Exchanger = strings.ToLower(x.Exchanger)
|
||||
x.Exchanger = CanonicalName(x.Exchanger)
|
||||
case *SRV:
|
||||
x.Target = strings.ToLower(x.Target)
|
||||
x.Target = CanonicalName(x.Target)
|
||||
case *DNAME:
|
||||
x.Target = strings.ToLower(x.Target)
|
||||
x.Target = CanonicalName(x.Target)
|
||||
}
|
||||
// 6.2. Canonical RR Form. (5) - origTTL
|
||||
wire := make([]byte, Len(r1)+1) // +1 to be safe(r)
|
||||
|
2
vendor/github.com/miekg/dns/doc.go
generated
vendored
2
vendor/github.com/miekg/dns/doc.go
generated
vendored
@ -209,7 +209,7 @@ Basic use pattern validating and replying to a message that has TSIG set.
|
||||
// *Msg r has an TSIG record and it was validated
|
||||
m.SetTsig("axfr.", dns.HmacMD5, 300, time.Now().Unix())
|
||||
} else {
|
||||
// *Msg r has an TSIG records and it was not valided
|
||||
// *Msg r has an TSIG records and it was not validated
|
||||
}
|
||||
}
|
||||
w.WriteMsg(m)
|
||||
|
5
vendor/github.com/miekg/dns/duplicate.go
generated
vendored
5
vendor/github.com/miekg/dns/duplicate.go
generated
vendored
@ -3,9 +3,8 @@ package dns
|
||||
//go:generate go run duplicate_generate.go
|
||||
|
||||
// IsDuplicate checks of r1 and r2 are duplicates of each other, excluding the TTL.
|
||||
// So this means the header data is equal *and* the RDATA is the same. Return true
|
||||
// is so, otherwise false.
|
||||
// It's a protocol violation to have identical RRs in a message.
|
||||
// So this means the header data is equal *and* the RDATA is the same. Returns true
|
||||
// if so, otherwise false. It's a protocol violation to have identical RRs in a message.
|
||||
func IsDuplicate(r1, r2 RR) bool {
|
||||
// Check whether the record header is identical.
|
||||
if !r1.Header().isDuplicate(r2.Header()) {
|
||||
|
26
vendor/github.com/miekg/dns/generate.go
generated
vendored
26
vendor/github.com/miekg/dns/generate.go
generated
vendored
@ -20,13 +20,13 @@ import (
|
||||
// of $ after that are interpreted.
|
||||
func (zp *ZoneParser) generate(l lex) (RR, bool) {
|
||||
token := l.token
|
||||
step := 1
|
||||
step := int64(1)
|
||||
if i := strings.IndexByte(token, '/'); i >= 0 {
|
||||
if i+1 == len(token) {
|
||||
return zp.setParseError("bad step in $GENERATE range", l)
|
||||
}
|
||||
|
||||
s, err := strconv.Atoi(token[i+1:])
|
||||
s, err := strconv.ParseInt(token[i+1:], 10, 64)
|
||||
if err != nil || s <= 0 {
|
||||
return zp.setParseError("bad step in $GENERATE range", l)
|
||||
}
|
||||
@ -40,12 +40,12 @@ func (zp *ZoneParser) generate(l lex) (RR, bool) {
|
||||
return zp.setParseError("bad start-stop in $GENERATE range", l)
|
||||
}
|
||||
|
||||
start, err := strconv.Atoi(sx[0])
|
||||
start, err := strconv.ParseInt(sx[0], 10, 64)
|
||||
if err != nil {
|
||||
return zp.setParseError("bad start in $GENERATE range", l)
|
||||
}
|
||||
|
||||
end, err := strconv.Atoi(sx[1])
|
||||
end, err := strconv.ParseInt(sx[1], 10, 64)
|
||||
if err != nil {
|
||||
return zp.setParseError("bad stop in $GENERATE range", l)
|
||||
}
|
||||
@ -75,10 +75,10 @@ func (zp *ZoneParser) generate(l lex) (RR, bool) {
|
||||
r := &generateReader{
|
||||
s: s,
|
||||
|
||||
cur: start,
|
||||
start: start,
|
||||
end: end,
|
||||
step: step,
|
||||
cur: int(start),
|
||||
start: int(start),
|
||||
end: int(end),
|
||||
step: int(step),
|
||||
|
||||
file: zp.file,
|
||||
lex: &l,
|
||||
@ -188,7 +188,7 @@ func (r *generateReader) ReadByte() (byte, error) {
|
||||
if errMsg != "" {
|
||||
return 0, r.parseError(errMsg, si+3+sep)
|
||||
}
|
||||
if r.start+offset < 0 || r.end+offset > 1<<31-1 {
|
||||
if r.start+offset < 0 || int64(r.end) + int64(offset) > 1<<31-1 {
|
||||
return 0, r.parseError("bad offset in $GENERATE", si+3+sep)
|
||||
}
|
||||
|
||||
@ -229,19 +229,19 @@ func modToPrintf(s string) (string, int, string) {
|
||||
return "", 0, "bad base in $GENERATE"
|
||||
}
|
||||
|
||||
offset, err := strconv.Atoi(offStr)
|
||||
offset, err := strconv.ParseInt(offStr, 10, 64)
|
||||
if err != nil {
|
||||
return "", 0, "bad offset in $GENERATE"
|
||||
}
|
||||
|
||||
width, err := strconv.Atoi(widthStr)
|
||||
width, err := strconv.ParseInt(widthStr, 10, 64)
|
||||
if err != nil || width < 0 || width > 255 {
|
||||
return "", 0, "bad width in $GENERATE"
|
||||
}
|
||||
|
||||
if width == 0 {
|
||||
return "%" + base, offset, ""
|
||||
return "%" + base, int(offset), ""
|
||||
}
|
||||
|
||||
return "%0" + widthStr + base, offset, ""
|
||||
return "%0" + widthStr + base, int(offset), ""
|
||||
}
|
||||
|
2
vendor/github.com/miekg/dns/labels.go
generated
vendored
2
vendor/github.com/miekg/dns/labels.go
generated
vendored
@ -83,7 +83,7 @@ func CompareDomainName(s1, s2 string) (n int) {
|
||||
return
|
||||
}
|
||||
|
||||
// CountLabel counts the the number of labels in the string s.
|
||||
// CountLabel counts the number of labels in the string s.
|
||||
// s must be a syntactically valid domain name.
|
||||
func CountLabel(s string) (labels int) {
|
||||
if s == "." {
|
||||
|
16
vendor/github.com/miekg/dns/msg.go
generated
vendored
16
vendor/github.com/miekg/dns/msg.go
generated
vendored
@ -398,17 +398,12 @@ Loop:
|
||||
return "", lenmsg, ErrLongDomain
|
||||
}
|
||||
for _, b := range msg[off : off+c] {
|
||||
switch b {
|
||||
case '.', '(', ')', ';', ' ', '@':
|
||||
fallthrough
|
||||
case '"', '\\':
|
||||
if isDomainNameLabelSpecial(b) {
|
||||
s = append(s, '\\', b)
|
||||
default:
|
||||
if b < ' ' || b > '~' { // unprintable, use \DDD
|
||||
s = append(s, escapeByte(b)...)
|
||||
} else {
|
||||
s = append(s, b)
|
||||
}
|
||||
} else if b < ' ' || b > '~' {
|
||||
s = append(s, escapeByte(b)...)
|
||||
} else {
|
||||
s = append(s, b)
|
||||
}
|
||||
}
|
||||
s = append(s, '.')
|
||||
@ -661,7 +656,6 @@ func unpackRRslice(l int, msg []byte, off int) (dst1 []RR, off1 int, err error)
|
||||
}
|
||||
// If offset does not increase anymore, l is a lie
|
||||
if off1 == off {
|
||||
l = i
|
||||
break
|
||||
}
|
||||
dst = append(dst, r)
|
||||
|
140
vendor/github.com/miekg/dns/msg_helpers.go
generated
vendored
140
vendor/github.com/miekg/dns/msg_helpers.go
generated
vendored
@ -423,86 +423,12 @@ Option:
|
||||
if off+int(optlen) > len(msg) {
|
||||
return nil, len(msg), &Error{err: "overflow unpacking opt"}
|
||||
}
|
||||
switch code {
|
||||
case EDNS0NSID:
|
||||
e := new(EDNS0_NSID)
|
||||
if err := e.unpack(msg[off : off+int(optlen)]); err != nil {
|
||||
return nil, len(msg), err
|
||||
}
|
||||
edns = append(edns, e)
|
||||
off += int(optlen)
|
||||
case EDNS0SUBNET:
|
||||
e := new(EDNS0_SUBNET)
|
||||
if err := e.unpack(msg[off : off+int(optlen)]); err != nil {
|
||||
return nil, len(msg), err
|
||||
}
|
||||
edns = append(edns, e)
|
||||
off += int(optlen)
|
||||
case EDNS0COOKIE:
|
||||
e := new(EDNS0_COOKIE)
|
||||
if err := e.unpack(msg[off : off+int(optlen)]); err != nil {
|
||||
return nil, len(msg), err
|
||||
}
|
||||
edns = append(edns, e)
|
||||
off += int(optlen)
|
||||
case EDNS0EXPIRE:
|
||||
e := new(EDNS0_EXPIRE)
|
||||
if err := e.unpack(msg[off : off+int(optlen)]); err != nil {
|
||||
return nil, len(msg), err
|
||||
}
|
||||
edns = append(edns, e)
|
||||
off += int(optlen)
|
||||
case EDNS0UL:
|
||||
e := new(EDNS0_UL)
|
||||
if err := e.unpack(msg[off : off+int(optlen)]); err != nil {
|
||||
return nil, len(msg), err
|
||||
}
|
||||
edns = append(edns, e)
|
||||
off += int(optlen)
|
||||
case EDNS0LLQ:
|
||||
e := new(EDNS0_LLQ)
|
||||
if err := e.unpack(msg[off : off+int(optlen)]); err != nil {
|
||||
return nil, len(msg), err
|
||||
}
|
||||
edns = append(edns, e)
|
||||
off += int(optlen)
|
||||
case EDNS0DAU:
|
||||
e := new(EDNS0_DAU)
|
||||
if err := e.unpack(msg[off : off+int(optlen)]); err != nil {
|
||||
return nil, len(msg), err
|
||||
}
|
||||
edns = append(edns, e)
|
||||
off += int(optlen)
|
||||
case EDNS0DHU:
|
||||
e := new(EDNS0_DHU)
|
||||
if err := e.unpack(msg[off : off+int(optlen)]); err != nil {
|
||||
return nil, len(msg), err
|
||||
}
|
||||
edns = append(edns, e)
|
||||
off += int(optlen)
|
||||
case EDNS0N3U:
|
||||
e := new(EDNS0_N3U)
|
||||
if err := e.unpack(msg[off : off+int(optlen)]); err != nil {
|
||||
return nil, len(msg), err
|
||||
}
|
||||
edns = append(edns, e)
|
||||
off += int(optlen)
|
||||
case EDNS0PADDING:
|
||||
e := new(EDNS0_PADDING)
|
||||
if err := e.unpack(msg[off : off+int(optlen)]); err != nil {
|
||||
return nil, len(msg), err
|
||||
}
|
||||
edns = append(edns, e)
|
||||
off += int(optlen)
|
||||
default:
|
||||
e := new(EDNS0_LOCAL)
|
||||
e.Code = code
|
||||
if err := e.unpack(msg[off : off+int(optlen)]); err != nil {
|
||||
return nil, len(msg), err
|
||||
}
|
||||
edns = append(edns, e)
|
||||
off += int(optlen)
|
||||
e := makeDataOpt(code)
|
||||
if err := e.unpack(msg[off : off+int(optlen)]); err != nil {
|
||||
return nil, len(msg), err
|
||||
}
|
||||
edns = append(edns, e)
|
||||
off += int(optlen)
|
||||
|
||||
if off < len(msg) {
|
||||
goto Option
|
||||
@ -511,6 +437,35 @@ Option:
|
||||
return edns, off, nil
|
||||
}
|
||||
|
||||
func makeDataOpt(code uint16) EDNS0 {
|
||||
switch code {
|
||||
case EDNS0NSID:
|
||||
return new(EDNS0_NSID)
|
||||
case EDNS0SUBNET:
|
||||
return new(EDNS0_SUBNET)
|
||||
case EDNS0COOKIE:
|
||||
return new(EDNS0_COOKIE)
|
||||
case EDNS0EXPIRE:
|
||||
return new(EDNS0_EXPIRE)
|
||||
case EDNS0UL:
|
||||
return new(EDNS0_UL)
|
||||
case EDNS0LLQ:
|
||||
return new(EDNS0_LLQ)
|
||||
case EDNS0DAU:
|
||||
return new(EDNS0_DAU)
|
||||
case EDNS0DHU:
|
||||
return new(EDNS0_DHU)
|
||||
case EDNS0N3U:
|
||||
return new(EDNS0_N3U)
|
||||
case EDNS0PADDING:
|
||||
return new(EDNS0_PADDING)
|
||||
default:
|
||||
e := new(EDNS0_LOCAL)
|
||||
e.Code = code
|
||||
return e
|
||||
}
|
||||
}
|
||||
|
||||
func packDataOpt(options []EDNS0, msg []byte, off int) (int, error) {
|
||||
for _, el := range options {
|
||||
b, err := el.pack()
|
||||
@ -521,9 +476,7 @@ func packDataOpt(options []EDNS0, msg []byte, off int) (int, error) {
|
||||
binary.BigEndian.PutUint16(msg[off+2:], uint16(len(b))) // Length
|
||||
off += 4
|
||||
if off+len(b) > len(msg) {
|
||||
copy(msg[off:], b)
|
||||
off = len(msg)
|
||||
continue
|
||||
return len(msg), &Error{err: "overflow packing opt"}
|
||||
}
|
||||
// Actual data
|
||||
copy(msg[off:off+len(b)], b)
|
||||
@ -783,28 +736,31 @@ func unpackDataAplPrefix(msg []byte, off int) (APLPrefix, int, error) {
|
||||
if int(prefix) > 8*len(ip) {
|
||||
return APLPrefix{}, len(msg), &Error{err: "APL prefix too long"}
|
||||
}
|
||||
|
||||
afdlen := int(nlen & 0x7f)
|
||||
if (int(prefix)+7)/8 != afdlen {
|
||||
return APLPrefix{}, len(msg), &Error{err: "invalid APL address length"}
|
||||
if afdlen > len(ip) {
|
||||
return APLPrefix{}, len(msg), &Error{err: "APL length too long"}
|
||||
}
|
||||
if off+afdlen > len(msg) {
|
||||
return APLPrefix{}, len(msg), &Error{err: "overflow unpacking APL address"}
|
||||
}
|
||||
off += copy(ip, msg[off:off+afdlen])
|
||||
if prefix%8 > 0 {
|
||||
if afdlen > 0 {
|
||||
last := ip[afdlen-1]
|
||||
zero := uint8(0xff) >> (prefix % 8)
|
||||
if last&zero > 0 {
|
||||
if last == 0 {
|
||||
return APLPrefix{}, len(msg), &Error{err: "extra APL address bits"}
|
||||
}
|
||||
}
|
||||
ipnet := net.IPNet{
|
||||
IP: ip,
|
||||
Mask: net.CIDRMask(int(prefix), 8*len(ip)),
|
||||
}
|
||||
network := ipnet.IP.Mask(ipnet.Mask)
|
||||
if !network.Equal(ipnet.IP) {
|
||||
return APLPrefix{}, len(msg), &Error{err: "invalid APL address length"}
|
||||
}
|
||||
|
||||
return APLPrefix{
|
||||
Negation: (nlen & 0x80) != 0,
|
||||
Network: net.IPNet{
|
||||
IP: ip,
|
||||
Mask: net.CIDRMask(int(prefix), 8*len(ip)),
|
||||
},
|
||||
Network: ipnet,
|
||||
}, off, nil
|
||||
}
|
||||
|
2
vendor/github.com/miekg/dns/msg_truncate.go
generated
vendored
2
vendor/github.com/miekg/dns/msg_truncate.go
generated
vendored
@ -73,7 +73,7 @@ func (dns *Msg) Truncate(size int) {
|
||||
|
||||
var numExtra int
|
||||
if l < size {
|
||||
l, numExtra = truncateLoop(dns.Extra, size, l, compression)
|
||||
_, numExtra = truncateLoop(dns.Extra, size, l, compression)
|
||||
}
|
||||
|
||||
// See the function documentation for when we set this.
|
||||
|
2
vendor/github.com/miekg/dns/nsecx.go
generated
vendored
2
vendor/github.com/miekg/dns/nsecx.go
generated
vendored
@ -43,7 +43,7 @@ func HashName(label string, ha uint8, iter uint16, salt string) string {
|
||||
return toBase32(nsec3)
|
||||
}
|
||||
|
||||
// Cover returns true if a name is covered by the NSEC3 record
|
||||
// Cover returns true if a name is covered by the NSEC3 record.
|
||||
func (rr *NSEC3) Cover(name string) bool {
|
||||
nameHash := HashName(name, rr.Hash, rr.Iterations, rr.Salt)
|
||||
owner := strings.ToUpper(rr.Hdr.Name)
|
||||
|
1
vendor/github.com/miekg/dns/privaterr.go
generated
vendored
1
vendor/github.com/miekg/dns/privaterr.go
generated
vendored
@ -13,7 +13,6 @@ type PrivateRdata interface {
|
||||
// Pack is used when packing a private RR into a buffer.
|
||||
Pack([]byte) (int, error)
|
||||
// Unpack is used when unpacking a private RR from a buffer.
|
||||
// TODO(miek): diff. signature than Pack, see edns0.go for instance.
|
||||
Unpack([]byte) (int, error)
|
||||
// Copy copies the Rdata into the PrivateRdata argument.
|
||||
Copy(PrivateRdata) error
|
||||
|
89
vendor/github.com/miekg/dns/scan.go
generated
vendored
89
vendor/github.com/miekg/dns/scan.go
generated
vendored
@ -87,31 +87,18 @@ type lex struct {
|
||||
column int // column in the file
|
||||
}
|
||||
|
||||
// Token holds the token that are returned when a zone file is parsed.
|
||||
type Token struct {
|
||||
// The scanned resource record when error is not nil.
|
||||
RR
|
||||
// When an error occurred, this has the error specifics.
|
||||
Error *ParseError
|
||||
// A potential comment positioned after the RR and on the same line.
|
||||
Comment string
|
||||
}
|
||||
|
||||
// ttlState describes the state necessary to fill in an omitted RR TTL
|
||||
type ttlState struct {
|
||||
ttl uint32 // ttl is the current default TTL
|
||||
isByDirective bool // isByDirective indicates whether ttl was set by a $TTL directive
|
||||
}
|
||||
|
||||
// NewRR reads the RR contained in the string s. Only the first RR is
|
||||
// returned. If s contains no records, NewRR will return nil with no
|
||||
// error.
|
||||
// NewRR reads the RR contained in the string s. Only the first RR is returned.
|
||||
// If s contains no records, NewRR will return nil with no error.
|
||||
//
|
||||
// The class defaults to IN and TTL defaults to 3600. The full zone
|
||||
// file syntax like $TTL, $ORIGIN, etc. is supported.
|
||||
//
|
||||
// All fields of the returned RR are set, except RR.Header().Rdlength
|
||||
// which is set to 0.
|
||||
// The class defaults to IN and TTL defaults to 3600. The full zone file syntax
|
||||
// like $TTL, $ORIGIN, etc. is supported. All fields of the returned RR are
|
||||
// set, except RR.Header().Rdlength which is set to 0.
|
||||
func NewRR(s string) (RR, error) {
|
||||
if len(s) > 0 && s[len(s)-1] != '\n' { // We need a closing newline
|
||||
return ReadRR(strings.NewReader(s+"\n"), "")
|
||||
@ -133,70 +120,6 @@ func ReadRR(r io.Reader, file string) (RR, error) {
|
||||
return rr, zp.Err()
|
||||
}
|
||||
|
||||
// ParseZone reads a RFC 1035 style zonefile from r. It returns
|
||||
// Tokens on the returned channel, each consisting of either a
|
||||
// parsed RR and optional comment or a nil RR and an error. The
|
||||
// channel is closed by ParseZone when the end of r is reached.
|
||||
//
|
||||
// The string file is used in error reporting and to resolve relative
|
||||
// $INCLUDE directives. The string origin is used as the initial
|
||||
// origin, as if the file would start with an $ORIGIN directive.
|
||||
//
|
||||
// The directives $INCLUDE, $ORIGIN, $TTL and $GENERATE are all
|
||||
// supported. Note that $GENERATE's range support up to a maximum of
|
||||
// of 65535 steps.
|
||||
//
|
||||
// Basic usage pattern when reading from a string (z) containing the
|
||||
// zone data:
|
||||
//
|
||||
// for x := range dns.ParseZone(strings.NewReader(z), "", "") {
|
||||
// if x.Error != nil {
|
||||
// // log.Println(x.Error)
|
||||
// } else {
|
||||
// // Do something with x.RR
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// Comments specified after an RR (and on the same line!) are
|
||||
// returned too:
|
||||
//
|
||||
// foo. IN A 10.0.0.1 ; this is a comment
|
||||
//
|
||||
// The text "; this is comment" is returned in Token.Comment.
|
||||
// Comments inside the RR are returned concatenated along with the
|
||||
// RR. Comments on a line by themselves are discarded.
|
||||
//
|
||||
// To prevent memory leaks it is important to always fully drain the
|
||||
// returned channel. If an error occurs, it will always be the last
|
||||
// Token sent on the channel.
|
||||
//
|
||||
// Deprecated: New users should prefer the ZoneParser API.
|
||||
func ParseZone(r io.Reader, origin, file string) chan *Token {
|
||||
t := make(chan *Token, 10000)
|
||||
go parseZone(r, origin, file, t)
|
||||
return t
|
||||
}
|
||||
|
||||
func parseZone(r io.Reader, origin, file string, t chan *Token) {
|
||||
defer close(t)
|
||||
|
||||
zp := NewZoneParser(r, origin, file)
|
||||
zp.SetIncludeAllowed(true)
|
||||
|
||||
for rr, ok := zp.Next(); ok; rr, ok = zp.Next() {
|
||||
t <- &Token{RR: rr, Comment: zp.Comment()}
|
||||
}
|
||||
|
||||
if err := zp.Err(); err != nil {
|
||||
pe, ok := err.(*ParseError)
|
||||
if !ok {
|
||||
pe = &ParseError{file: file, err: err.Error()}
|
||||
}
|
||||
|
||||
t <- &Token{Error: pe}
|
||||
}
|
||||
}
|
||||
|
||||
// ZoneParser is a parser for an RFC 1035 style zonefile.
|
||||
//
|
||||
// Each parsed RR in the zone is returned sequentially from Next. An
|
||||
@ -247,7 +170,7 @@ type ZoneParser struct {
|
||||
|
||||
includeDepth uint8
|
||||
|
||||
includeAllowed bool
|
||||
includeAllowed bool
|
||||
generateDisallowed bool
|
||||
}
|
||||
|
||||
|
291
vendor/github.com/miekg/dns/scan_rr.go
generated
vendored
291
vendor/github.com/miekg/dns/scan_rr.go
generated
vendored
@ -1,6 +1,7 @@
|
||||
package dns
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"net"
|
||||
"strconv"
|
||||
@ -10,15 +11,15 @@ import (
|
||||
// A remainder of the rdata with embedded spaces, return the parsed string (sans the spaces)
|
||||
// or an error
|
||||
func endingToString(c *zlexer, errstr string) (string, *ParseError) {
|
||||
var s string
|
||||
var buffer bytes.Buffer
|
||||
l, _ := c.Next() // zString
|
||||
for l.value != zNewline && l.value != zEOF {
|
||||
if l.err {
|
||||
return s, &ParseError{"", errstr, l}
|
||||
return buffer.String(), &ParseError{"", errstr, l}
|
||||
}
|
||||
switch l.value {
|
||||
case zString:
|
||||
s += l.token
|
||||
buffer.WriteString(l.token)
|
||||
case zBlank: // Ok
|
||||
default:
|
||||
return "", &ParseError{"", errstr, l}
|
||||
@ -26,7 +27,7 @@ func endingToString(c *zlexer, errstr string) (string, *ParseError) {
|
||||
l, _ = c.Next()
|
||||
}
|
||||
|
||||
return s, nil
|
||||
return buffer.String(), nil
|
||||
}
|
||||
|
||||
// A remainder of the rdata with embedded spaces, split on unquoted whitespace
|
||||
@ -403,7 +404,7 @@ func (rr *SOA) parse(c *zlexer, o string) *ParseError {
|
||||
if l.err {
|
||||
return &ParseError{"", "bad SOA zone parameter", l}
|
||||
}
|
||||
if j, e := strconv.ParseUint(l.token, 10, 32); e != nil {
|
||||
if j, err := strconv.ParseUint(l.token, 10, 32); err != nil {
|
||||
if i == 0 {
|
||||
// Serial must be a number
|
||||
return &ParseError{"", "bad SOA zone parameter", l}
|
||||
@ -446,16 +447,16 @@ func (rr *SRV) parse(c *zlexer, o string) *ParseError {
|
||||
|
||||
c.Next() // zBlank
|
||||
l, _ = c.Next() // zString
|
||||
i, e = strconv.ParseUint(l.token, 10, 16)
|
||||
if e != nil || l.err {
|
||||
i, e1 := strconv.ParseUint(l.token, 10, 16)
|
||||
if e1 != nil || l.err {
|
||||
return &ParseError{"", "bad SRV Weight", l}
|
||||
}
|
||||
rr.Weight = uint16(i)
|
||||
|
||||
c.Next() // zBlank
|
||||
l, _ = c.Next() // zString
|
||||
i, e = strconv.ParseUint(l.token, 10, 16)
|
||||
if e != nil || l.err {
|
||||
i, e2 := strconv.ParseUint(l.token, 10, 16)
|
||||
if e2 != nil || l.err {
|
||||
return &ParseError{"", "bad SRV Port", l}
|
||||
}
|
||||
rr.Port = uint16(i)
|
||||
@ -482,8 +483,8 @@ func (rr *NAPTR) parse(c *zlexer, o string) *ParseError {
|
||||
|
||||
c.Next() // zBlank
|
||||
l, _ = c.Next() // zString
|
||||
i, e = strconv.ParseUint(l.token, 10, 16)
|
||||
if e != nil || l.err {
|
||||
i, e1 := strconv.ParseUint(l.token, 10, 16)
|
||||
if e1 != nil || l.err {
|
||||
return &ParseError{"", "bad NAPTR Preference", l}
|
||||
}
|
||||
rr.Preference = uint16(i)
|
||||
@ -581,9 +582,9 @@ func (rr *TALINK) parse(c *zlexer, o string) *ParseError {
|
||||
|
||||
func (rr *LOC) parse(c *zlexer, o string) *ParseError {
|
||||
// Non zero defaults for LOC record, see RFC 1876, Section 3.
|
||||
rr.HorizPre = 165 // 10000
|
||||
rr.VertPre = 162 // 10
|
||||
rr.Size = 18 // 1
|
||||
rr.Size = 0x12 // 1e2 cm (1m)
|
||||
rr.HorizPre = 0x16 // 1e6 cm (10000m)
|
||||
rr.VertPre = 0x13 // 1e3 cm (10m)
|
||||
ok := false
|
||||
|
||||
// North
|
||||
@ -600,15 +601,15 @@ func (rr *LOC) parse(c *zlexer, o string) *ParseError {
|
||||
if rr.Latitude, ok = locCheckNorth(l.token, rr.Latitude); ok {
|
||||
goto East
|
||||
}
|
||||
i, e = strconv.ParseUint(l.token, 10, 32)
|
||||
if e != nil || l.err {
|
||||
if i, err := strconv.ParseUint(l.token, 10, 32); err != nil || l.err {
|
||||
return &ParseError{"", "bad LOC Latitude minutes", l}
|
||||
} else {
|
||||
rr.Latitude += 1000 * 60 * uint32(i)
|
||||
}
|
||||
rr.Latitude += 1000 * 60 * uint32(i)
|
||||
|
||||
c.Next() // zBlank
|
||||
l, _ = c.Next()
|
||||
if i, e := strconv.ParseFloat(l.token, 32); e != nil || l.err {
|
||||
if i, err := strconv.ParseFloat(l.token, 32); err != nil || l.err {
|
||||
return &ParseError{"", "bad LOC Latitude seconds", l}
|
||||
} else {
|
||||
rr.Latitude += uint32(1000 * i)
|
||||
@ -626,7 +627,7 @@ East:
|
||||
// East
|
||||
c.Next() // zBlank
|
||||
l, _ = c.Next()
|
||||
if i, e := strconv.ParseUint(l.token, 10, 32); e != nil || l.err {
|
||||
if i, err := strconv.ParseUint(l.token, 10, 32); err != nil || l.err {
|
||||
return &ParseError{"", "bad LOC Longitude", l}
|
||||
} else {
|
||||
rr.Longitude = 1000 * 60 * 60 * uint32(i)
|
||||
@ -637,14 +638,14 @@ East:
|
||||
if rr.Longitude, ok = locCheckEast(l.token, rr.Longitude); ok {
|
||||
goto Altitude
|
||||
}
|
||||
if i, e := strconv.ParseUint(l.token, 10, 32); e != nil || l.err {
|
||||
if i, err := strconv.ParseUint(l.token, 10, 32); err != nil || l.err {
|
||||
return &ParseError{"", "bad LOC Longitude minutes", l}
|
||||
} else {
|
||||
rr.Longitude += 1000 * 60 * uint32(i)
|
||||
}
|
||||
c.Next() // zBlank
|
||||
l, _ = c.Next()
|
||||
if i, e := strconv.ParseFloat(l.token, 32); e != nil || l.err {
|
||||
if i, err := strconv.ParseFloat(l.token, 32); err != nil || l.err {
|
||||
return &ParseError{"", "bad LOC Longitude seconds", l}
|
||||
} else {
|
||||
rr.Longitude += uint32(1000 * i)
|
||||
@ -667,7 +668,7 @@ Altitude:
|
||||
if l.token[len(l.token)-1] == 'M' || l.token[len(l.token)-1] == 'm' {
|
||||
l.token = l.token[0 : len(l.token)-1]
|
||||
}
|
||||
if i, e := strconv.ParseFloat(l.token, 32); e != nil {
|
||||
if i, err := strconv.ParseFloat(l.token, 32); err != nil {
|
||||
return &ParseError{"", "bad LOC Altitude", l}
|
||||
} else {
|
||||
rr.Altitude = uint32(i*100.0 + 10000000.0 + 0.5)
|
||||
@ -681,23 +682,23 @@ Altitude:
|
||||
case zString:
|
||||
switch count {
|
||||
case 0: // Size
|
||||
e, m, ok := stringToCm(l.token)
|
||||
exp, m, ok := stringToCm(l.token)
|
||||
if !ok {
|
||||
return &ParseError{"", "bad LOC Size", l}
|
||||
}
|
||||
rr.Size = e&0x0f | m<<4&0xf0
|
||||
rr.Size = exp&0x0f | m<<4&0xf0
|
||||
case 1: // HorizPre
|
||||
e, m, ok := stringToCm(l.token)
|
||||
exp, m, ok := stringToCm(l.token)
|
||||
if !ok {
|
||||
return &ParseError{"", "bad LOC HorizPre", l}
|
||||
}
|
||||
rr.HorizPre = e&0x0f | m<<4&0xf0
|
||||
rr.HorizPre = exp&0x0f | m<<4&0xf0
|
||||
case 2: // VertPre
|
||||
e, m, ok := stringToCm(l.token)
|
||||
exp, m, ok := stringToCm(l.token)
|
||||
if !ok {
|
||||
return &ParseError{"", "bad LOC VertPre", l}
|
||||
}
|
||||
rr.VertPre = e&0x0f | m<<4&0xf0
|
||||
rr.VertPre = exp&0x0f | m<<4&0xf0
|
||||
}
|
||||
count++
|
||||
case zBlank:
|
||||
@ -762,7 +763,7 @@ func (rr *CERT) parse(c *zlexer, o string) *ParseError {
|
||||
l, _ := c.Next()
|
||||
if v, ok := StringToCertType[l.token]; ok {
|
||||
rr.Type = v
|
||||
} else if i, e := strconv.ParseUint(l.token, 10, 16); e != nil {
|
||||
} else if i, err := strconv.ParseUint(l.token, 10, 16); err != nil {
|
||||
return &ParseError{"", "bad CERT Type", l}
|
||||
} else {
|
||||
rr.Type = uint16(i)
|
||||
@ -778,7 +779,7 @@ func (rr *CERT) parse(c *zlexer, o string) *ParseError {
|
||||
l, _ = c.Next() // zString
|
||||
if v, ok := StringToAlgorithm[l.token]; ok {
|
||||
rr.Algorithm = v
|
||||
} else if i, e := strconv.ParseUint(l.token, 10, 8); e != nil {
|
||||
} else if i, err := strconv.ParseUint(l.token, 10, 8); err != nil {
|
||||
return &ParseError{"", "bad CERT Algorithm", l}
|
||||
} else {
|
||||
rr.Algorithm = uint8(i)
|
||||
@ -812,8 +813,8 @@ func (rr *CSYNC) parse(c *zlexer, o string) *ParseError {
|
||||
c.Next() // zBlank
|
||||
|
||||
l, _ = c.Next()
|
||||
j, e = strconv.ParseUint(l.token, 10, 16)
|
||||
if e != nil {
|
||||
j, e1 := strconv.ParseUint(l.token, 10, 16)
|
||||
if e1 != nil {
|
||||
// Serial must be a number
|
||||
return &ParseError{"", "bad CSYNC flags", l}
|
||||
}
|
||||
@ -845,9 +846,7 @@ func (rr *CSYNC) parse(c *zlexer, o string) *ParseError {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (rr *SIG) parse(c *zlexer, o string) *ParseError {
|
||||
return rr.RRSIG.parse(c, o)
|
||||
}
|
||||
func (rr *SIG) parse(c *zlexer, o string) *ParseError { return rr.RRSIG.parse(c, o) }
|
||||
|
||||
func (rr *RRSIG) parse(c *zlexer, o string) *ParseError {
|
||||
l, _ := c.Next()
|
||||
@ -868,24 +867,24 @@ func (rr *RRSIG) parse(c *zlexer, o string) *ParseError {
|
||||
|
||||
c.Next() // zBlank
|
||||
l, _ = c.Next()
|
||||
i, err := strconv.ParseUint(l.token, 10, 8)
|
||||
if err != nil || l.err {
|
||||
i, e := strconv.ParseUint(l.token, 10, 8)
|
||||
if e != nil || l.err {
|
||||
return &ParseError{"", "bad RRSIG Algorithm", l}
|
||||
}
|
||||
rr.Algorithm = uint8(i)
|
||||
|
||||
c.Next() // zBlank
|
||||
l, _ = c.Next()
|
||||
i, err = strconv.ParseUint(l.token, 10, 8)
|
||||
if err != nil || l.err {
|
||||
i, e1 := strconv.ParseUint(l.token, 10, 8)
|
||||
if e1 != nil || l.err {
|
||||
return &ParseError{"", "bad RRSIG Labels", l}
|
||||
}
|
||||
rr.Labels = uint8(i)
|
||||
|
||||
c.Next() // zBlank
|
||||
l, _ = c.Next()
|
||||
i, err = strconv.ParseUint(l.token, 10, 32)
|
||||
if err != nil || l.err {
|
||||
i, e2 := strconv.ParseUint(l.token, 10, 32)
|
||||
if e2 != nil || l.err {
|
||||
return &ParseError{"", "bad RRSIG OrigTtl", l}
|
||||
}
|
||||
rr.OrigTtl = uint32(i)
|
||||
@ -918,8 +917,8 @@ func (rr *RRSIG) parse(c *zlexer, o string) *ParseError {
|
||||
|
||||
c.Next() // zBlank
|
||||
l, _ = c.Next()
|
||||
i, err = strconv.ParseUint(l.token, 10, 16)
|
||||
if err != nil || l.err {
|
||||
i, e3 := strconv.ParseUint(l.token, 10, 16)
|
||||
if e3 != nil || l.err {
|
||||
return &ParseError{"", "bad RRSIG KeyTag", l}
|
||||
}
|
||||
rr.KeyTag = uint16(i)
|
||||
@ -933,9 +932,9 @@ func (rr *RRSIG) parse(c *zlexer, o string) *ParseError {
|
||||
}
|
||||
rr.SignerName = name
|
||||
|
||||
s, e := endingToString(c, "bad RRSIG Signature")
|
||||
if e != nil {
|
||||
return e
|
||||
s, e4 := endingToString(c, "bad RRSIG Signature")
|
||||
if e4 != nil {
|
||||
return e4
|
||||
}
|
||||
rr.Signature = s
|
||||
|
||||
@ -985,15 +984,15 @@ func (rr *NSEC3) parse(c *zlexer, o string) *ParseError {
|
||||
rr.Hash = uint8(i)
|
||||
c.Next() // zBlank
|
||||
l, _ = c.Next()
|
||||
i, e = strconv.ParseUint(l.token, 10, 8)
|
||||
if e != nil || l.err {
|
||||
i, e1 := strconv.ParseUint(l.token, 10, 8)
|
||||
if e1 != nil || l.err {
|
||||
return &ParseError{"", "bad NSEC3 Flags", l}
|
||||
}
|
||||
rr.Flags = uint8(i)
|
||||
c.Next() // zBlank
|
||||
l, _ = c.Next()
|
||||
i, e = strconv.ParseUint(l.token, 10, 16)
|
||||
if e != nil || l.err {
|
||||
i, e2 := strconv.ParseUint(l.token, 10, 16)
|
||||
if e2 != nil || l.err {
|
||||
return &ParseError{"", "bad NSEC3 Iterations", l}
|
||||
}
|
||||
rr.Iterations = uint16(i)
|
||||
@ -1050,22 +1049,22 @@ func (rr *NSEC3PARAM) parse(c *zlexer, o string) *ParseError {
|
||||
rr.Hash = uint8(i)
|
||||
c.Next() // zBlank
|
||||
l, _ = c.Next()
|
||||
i, e = strconv.ParseUint(l.token, 10, 8)
|
||||
if e != nil || l.err {
|
||||
i, e1 := strconv.ParseUint(l.token, 10, 8)
|
||||
if e1 != nil || l.err {
|
||||
return &ParseError{"", "bad NSEC3PARAM Flags", l}
|
||||
}
|
||||
rr.Flags = uint8(i)
|
||||
c.Next() // zBlank
|
||||
l, _ = c.Next()
|
||||
i, e = strconv.ParseUint(l.token, 10, 16)
|
||||
if e != nil || l.err {
|
||||
i, e2 := strconv.ParseUint(l.token, 10, 16)
|
||||
if e2 != nil || l.err {
|
||||
return &ParseError{"", "bad NSEC3PARAM Iterations", l}
|
||||
}
|
||||
rr.Iterations = uint16(i)
|
||||
c.Next()
|
||||
l, _ = c.Next()
|
||||
if l.token != "-" {
|
||||
rr.SaltLength = uint8(len(l.token))
|
||||
rr.SaltLength = uint8(len(l.token) / 2)
|
||||
rr.Salt = l.token
|
||||
}
|
||||
return slurpRemainder(c)
|
||||
@ -1132,15 +1131,15 @@ func (rr *SSHFP) parse(c *zlexer, o string) *ParseError {
|
||||
rr.Algorithm = uint8(i)
|
||||
c.Next() // zBlank
|
||||
l, _ = c.Next()
|
||||
i, e = strconv.ParseUint(l.token, 10, 8)
|
||||
if e != nil || l.err {
|
||||
i, e1 := strconv.ParseUint(l.token, 10, 8)
|
||||
if e1 != nil || l.err {
|
||||
return &ParseError{"", "bad SSHFP Type", l}
|
||||
}
|
||||
rr.Type = uint8(i)
|
||||
c.Next() // zBlank
|
||||
s, e1 := endingToString(c, "bad SSHFP Fingerprint")
|
||||
if e1 != nil {
|
||||
return e1
|
||||
s, e2 := endingToString(c, "bad SSHFP Fingerprint")
|
||||
if e2 != nil {
|
||||
return e2
|
||||
}
|
||||
rr.FingerPrint = s
|
||||
return nil
|
||||
@ -1155,37 +1154,32 @@ func (rr *DNSKEY) parseDNSKEY(c *zlexer, o, typ string) *ParseError {
|
||||
rr.Flags = uint16(i)
|
||||
c.Next() // zBlank
|
||||
l, _ = c.Next() // zString
|
||||
i, e = strconv.ParseUint(l.token, 10, 8)
|
||||
if e != nil || l.err {
|
||||
i, e1 := strconv.ParseUint(l.token, 10, 8)
|
||||
if e1 != nil || l.err {
|
||||
return &ParseError{"", "bad " + typ + " Protocol", l}
|
||||
}
|
||||
rr.Protocol = uint8(i)
|
||||
c.Next() // zBlank
|
||||
l, _ = c.Next() // zString
|
||||
i, e = strconv.ParseUint(l.token, 10, 8)
|
||||
if e != nil || l.err {
|
||||
i, e2 := strconv.ParseUint(l.token, 10, 8)
|
||||
if e2 != nil || l.err {
|
||||
return &ParseError{"", "bad " + typ + " Algorithm", l}
|
||||
}
|
||||
rr.Algorithm = uint8(i)
|
||||
s, e1 := endingToString(c, "bad "+typ+" PublicKey")
|
||||
if e1 != nil {
|
||||
return e1
|
||||
s, e3 := endingToString(c, "bad "+typ+" PublicKey")
|
||||
if e3 != nil {
|
||||
return e3
|
||||
}
|
||||
rr.PublicKey = s
|
||||
return nil
|
||||
}
|
||||
|
||||
func (rr *DNSKEY) parse(c *zlexer, o string) *ParseError {
|
||||
return rr.parseDNSKEY(c, o, "DNSKEY")
|
||||
}
|
||||
|
||||
func (rr *KEY) parse(c *zlexer, o string) *ParseError {
|
||||
return rr.parseDNSKEY(c, o, "KEY")
|
||||
}
|
||||
|
||||
func (rr *CDNSKEY) parse(c *zlexer, o string) *ParseError {
|
||||
return rr.parseDNSKEY(c, o, "CDNSKEY")
|
||||
}
|
||||
func (rr *DNSKEY) parse(c *zlexer, o string) *ParseError { return rr.parseDNSKEY(c, o, "DNSKEY") }
|
||||
func (rr *KEY) parse(c *zlexer, o string) *ParseError { return rr.parseDNSKEY(c, o, "KEY") }
|
||||
func (rr *CDNSKEY) parse(c *zlexer, o string) *ParseError { return rr.parseDNSKEY(c, o, "CDNSKEY") }
|
||||
func (rr *DS) parse(c *zlexer, o string) *ParseError { return rr.parseDS(c, o, "DS") }
|
||||
func (rr *DLV) parse(c *zlexer, o string) *ParseError { return rr.parseDS(c, o, "DLV") }
|
||||
func (rr *CDS) parse(c *zlexer, o string) *ParseError { return rr.parseDS(c, o, "CDS") }
|
||||
|
||||
func (rr *RKEY) parse(c *zlexer, o string) *ParseError {
|
||||
l, _ := c.Next()
|
||||
@ -1196,21 +1190,21 @@ func (rr *RKEY) parse(c *zlexer, o string) *ParseError {
|
||||
rr.Flags = uint16(i)
|
||||
c.Next() // zBlank
|
||||
l, _ = c.Next() // zString
|
||||
i, e = strconv.ParseUint(l.token, 10, 8)
|
||||
if e != nil || l.err {
|
||||
i, e1 := strconv.ParseUint(l.token, 10, 8)
|
||||
if e1 != nil || l.err {
|
||||
return &ParseError{"", "bad RKEY Protocol", l}
|
||||
}
|
||||
rr.Protocol = uint8(i)
|
||||
c.Next() // zBlank
|
||||
l, _ = c.Next() // zString
|
||||
i, e = strconv.ParseUint(l.token, 10, 8)
|
||||
if e != nil || l.err {
|
||||
i, e2 := strconv.ParseUint(l.token, 10, 8)
|
||||
if e2 != nil || l.err {
|
||||
return &ParseError{"", "bad RKEY Algorithm", l}
|
||||
}
|
||||
rr.Algorithm = uint8(i)
|
||||
s, e1 := endingToString(c, "bad RKEY PublicKey")
|
||||
if e1 != nil {
|
||||
return e1
|
||||
s, e3 := endingToString(c, "bad RKEY PublicKey")
|
||||
if e3 != nil {
|
||||
return e3
|
||||
}
|
||||
rr.PublicKey = s
|
||||
return nil
|
||||
@ -1243,15 +1237,15 @@ func (rr *GPOS) parse(c *zlexer, o string) *ParseError {
|
||||
rr.Longitude = l.token
|
||||
c.Next() // zBlank
|
||||
l, _ = c.Next()
|
||||
_, e = strconv.ParseFloat(l.token, 64)
|
||||
if e != nil || l.err {
|
||||
_, e1 := strconv.ParseFloat(l.token, 64)
|
||||
if e1 != nil || l.err {
|
||||
return &ParseError{"", "bad GPOS Latitude", l}
|
||||
}
|
||||
rr.Latitude = l.token
|
||||
c.Next() // zBlank
|
||||
l, _ = c.Next()
|
||||
_, e = strconv.ParseFloat(l.token, 64)
|
||||
if e != nil || l.err {
|
||||
_, e2 := strconv.ParseFloat(l.token, 64)
|
||||
if e2 != nil || l.err {
|
||||
return &ParseError{"", "bad GPOS Altitude", l}
|
||||
}
|
||||
rr.Altitude = l.token
|
||||
@ -1267,7 +1261,7 @@ func (rr *DS) parseDS(c *zlexer, o, typ string) *ParseError {
|
||||
rr.KeyTag = uint16(i)
|
||||
c.Next() // zBlank
|
||||
l, _ = c.Next()
|
||||
if i, e = strconv.ParseUint(l.token, 10, 8); e != nil {
|
||||
if i, err := strconv.ParseUint(l.token, 10, 8); err != nil {
|
||||
tokenUpper := strings.ToUpper(l.token)
|
||||
i, ok := StringToAlgorithm[tokenUpper]
|
||||
if !ok || l.err {
|
||||
@ -1279,31 +1273,19 @@ func (rr *DS) parseDS(c *zlexer, o, typ string) *ParseError {
|
||||
}
|
||||
c.Next() // zBlank
|
||||
l, _ = c.Next()
|
||||
i, e = strconv.ParseUint(l.token, 10, 8)
|
||||
if e != nil || l.err {
|
||||
i, e1 := strconv.ParseUint(l.token, 10, 8)
|
||||
if e1 != nil || l.err {
|
||||
return &ParseError{"", "bad " + typ + " DigestType", l}
|
||||
}
|
||||
rr.DigestType = uint8(i)
|
||||
s, e1 := endingToString(c, "bad "+typ+" Digest")
|
||||
if e1 != nil {
|
||||
return e1
|
||||
s, e2 := endingToString(c, "bad "+typ+" Digest")
|
||||
if e2 != nil {
|
||||
return e2
|
||||
}
|
||||
rr.Digest = s
|
||||
return nil
|
||||
}
|
||||
|
||||
func (rr *DS) parse(c *zlexer, o string) *ParseError {
|
||||
return rr.parseDS(c, o, "DS")
|
||||
}
|
||||
|
||||
func (rr *DLV) parse(c *zlexer, o string) *ParseError {
|
||||
return rr.parseDS(c, o, "DLV")
|
||||
}
|
||||
|
||||
func (rr *CDS) parse(c *zlexer, o string) *ParseError {
|
||||
return rr.parseDS(c, o, "CDS")
|
||||
}
|
||||
|
||||
func (rr *TA) parse(c *zlexer, o string) *ParseError {
|
||||
l, _ := c.Next()
|
||||
i, e := strconv.ParseUint(l.token, 10, 16)
|
||||
@ -1313,7 +1295,7 @@ func (rr *TA) parse(c *zlexer, o string) *ParseError {
|
||||
rr.KeyTag = uint16(i)
|
||||
c.Next() // zBlank
|
||||
l, _ = c.Next()
|
||||
if i, e := strconv.ParseUint(l.token, 10, 8); e != nil {
|
||||
if i, err := strconv.ParseUint(l.token, 10, 8); err != nil {
|
||||
tokenUpper := strings.ToUpper(l.token)
|
||||
i, ok := StringToAlgorithm[tokenUpper]
|
||||
if !ok || l.err {
|
||||
@ -1325,14 +1307,14 @@ func (rr *TA) parse(c *zlexer, o string) *ParseError {
|
||||
}
|
||||
c.Next() // zBlank
|
||||
l, _ = c.Next()
|
||||
i, e = strconv.ParseUint(l.token, 10, 8)
|
||||
if e != nil || l.err {
|
||||
i, e1 := strconv.ParseUint(l.token, 10, 8)
|
||||
if e1 != nil || l.err {
|
||||
return &ParseError{"", "bad TA DigestType", l}
|
||||
}
|
||||
rr.DigestType = uint8(i)
|
||||
s, err := endingToString(c, "bad TA Digest")
|
||||
if err != nil {
|
||||
return err
|
||||
s, e2 := endingToString(c, "bad TA Digest")
|
||||
if e2 != nil {
|
||||
return e2
|
||||
}
|
||||
rr.Digest = s
|
||||
return nil
|
||||
@ -1347,22 +1329,22 @@ func (rr *TLSA) parse(c *zlexer, o string) *ParseError {
|
||||
rr.Usage = uint8(i)
|
||||
c.Next() // zBlank
|
||||
l, _ = c.Next()
|
||||
i, e = strconv.ParseUint(l.token, 10, 8)
|
||||
if e != nil || l.err {
|
||||
i, e1 := strconv.ParseUint(l.token, 10, 8)
|
||||
if e1 != nil || l.err {
|
||||
return &ParseError{"", "bad TLSA Selector", l}
|
||||
}
|
||||
rr.Selector = uint8(i)
|
||||
c.Next() // zBlank
|
||||
l, _ = c.Next()
|
||||
i, e = strconv.ParseUint(l.token, 10, 8)
|
||||
if e != nil || l.err {
|
||||
i, e2 := strconv.ParseUint(l.token, 10, 8)
|
||||
if e2 != nil || l.err {
|
||||
return &ParseError{"", "bad TLSA MatchingType", l}
|
||||
}
|
||||
rr.MatchingType = uint8(i)
|
||||
// So this needs be e2 (i.e. different than e), because...??t
|
||||
s, e2 := endingToString(c, "bad TLSA Certificate")
|
||||
if e2 != nil {
|
||||
return e2
|
||||
s, e3 := endingToString(c, "bad TLSA Certificate")
|
||||
if e3 != nil {
|
||||
return e3
|
||||
}
|
||||
rr.Certificate = s
|
||||
return nil
|
||||
@ -1377,22 +1359,22 @@ func (rr *SMIMEA) parse(c *zlexer, o string) *ParseError {
|
||||
rr.Usage = uint8(i)
|
||||
c.Next() // zBlank
|
||||
l, _ = c.Next()
|
||||
i, e = strconv.ParseUint(l.token, 10, 8)
|
||||
if e != nil || l.err {
|
||||
i, e1 := strconv.ParseUint(l.token, 10, 8)
|
||||
if e1 != nil || l.err {
|
||||
return &ParseError{"", "bad SMIMEA Selector", l}
|
||||
}
|
||||
rr.Selector = uint8(i)
|
||||
c.Next() // zBlank
|
||||
l, _ = c.Next()
|
||||
i, e = strconv.ParseUint(l.token, 10, 8)
|
||||
if e != nil || l.err {
|
||||
i, e2 := strconv.ParseUint(l.token, 10, 8)
|
||||
if e2 != nil || l.err {
|
||||
return &ParseError{"", "bad SMIMEA MatchingType", l}
|
||||
}
|
||||
rr.MatchingType = uint8(i)
|
||||
// So this needs be e2 (i.e. different than e), because...??t
|
||||
s, e2 := endingToString(c, "bad SMIMEA Certificate")
|
||||
if e2 != nil {
|
||||
return e2
|
||||
s, e3 := endingToString(c, "bad SMIMEA Certificate")
|
||||
if e3 != nil {
|
||||
return e3
|
||||
}
|
||||
rr.Certificate = s
|
||||
return nil
|
||||
@ -1469,16 +1451,16 @@ func (rr *URI) parse(c *zlexer, o string) *ParseError {
|
||||
rr.Priority = uint16(i)
|
||||
c.Next() // zBlank
|
||||
l, _ = c.Next()
|
||||
i, e = strconv.ParseUint(l.token, 10, 16)
|
||||
if e != nil || l.err {
|
||||
i, e1 := strconv.ParseUint(l.token, 10, 16)
|
||||
if e1 != nil || l.err {
|
||||
return &ParseError{"", "bad URI Weight", l}
|
||||
}
|
||||
rr.Weight = uint16(i)
|
||||
|
||||
c.Next() // zBlank
|
||||
s, err := endingToTxtSlice(c, "bad URI Target")
|
||||
if err != nil {
|
||||
return err
|
||||
s, e2 := endingToTxtSlice(c, "bad URI Target")
|
||||
if e2 != nil {
|
||||
return e2
|
||||
}
|
||||
if len(s) != 1 {
|
||||
return &ParseError{"", "bad URI Target", l}
|
||||
@ -1506,9 +1488,9 @@ func (rr *NID) parse(c *zlexer, o string) *ParseError {
|
||||
rr.Preference = uint16(i)
|
||||
c.Next() // zBlank
|
||||
l, _ = c.Next() // zString
|
||||
u, err := stringToNodeID(l)
|
||||
if err != nil || l.err {
|
||||
return err
|
||||
u, e1 := stringToNodeID(l)
|
||||
if e1 != nil || l.err {
|
||||
return e1
|
||||
}
|
||||
rr.NodeID = u
|
||||
return slurpRemainder(c)
|
||||
@ -1546,7 +1528,6 @@ func (rr *LP) parse(c *zlexer, o string) *ParseError {
|
||||
return &ParseError{"", "bad LP Fqdn", l}
|
||||
}
|
||||
rr.Fqdn = name
|
||||
|
||||
return slurpRemainder(c)
|
||||
}
|
||||
|
||||
@ -1559,9 +1540,9 @@ func (rr *L64) parse(c *zlexer, o string) *ParseError {
|
||||
rr.Preference = uint16(i)
|
||||
c.Next() // zBlank
|
||||
l, _ = c.Next() // zString
|
||||
u, err := stringToNodeID(l)
|
||||
if err != nil || l.err {
|
||||
return err
|
||||
u, e1 := stringToNodeID(l)
|
||||
if e1 != nil || l.err {
|
||||
return e1
|
||||
}
|
||||
rr.Locator64 = u
|
||||
return slurpRemainder(c)
|
||||
@ -1624,14 +1605,13 @@ func (rr *PX) parse(c *zlexer, o string) *ParseError {
|
||||
return &ParseError{"", "bad PX Mapx400", l}
|
||||
}
|
||||
rr.Mapx400 = mapx400
|
||||
|
||||
return slurpRemainder(c)
|
||||
}
|
||||
|
||||
func (rr *CAA) parse(c *zlexer, o string) *ParseError {
|
||||
l, _ := c.Next()
|
||||
i, err := strconv.ParseUint(l.token, 10, 8)
|
||||
if err != nil || l.err {
|
||||
i, e := strconv.ParseUint(l.token, 10, 8)
|
||||
if e != nil || l.err {
|
||||
return &ParseError{"", "bad CAA Flag", l}
|
||||
}
|
||||
rr.Flag = uint8(i)
|
||||
@ -1644,9 +1624,9 @@ func (rr *CAA) parse(c *zlexer, o string) *ParseError {
|
||||
rr.Tag = l.token
|
||||
|
||||
c.Next() // zBlank
|
||||
s, e := endingToTxtSlice(c, "bad CAA Value")
|
||||
if e != nil {
|
||||
return e
|
||||
s, e1 := endingToTxtSlice(c, "bad CAA Value")
|
||||
if e1 != nil {
|
||||
return e1
|
||||
}
|
||||
if len(s) != 1 {
|
||||
return &ParseError{"", "bad CAA Value", l}
|
||||
@ -1667,8 +1647,8 @@ func (rr *TKEY) parse(c *zlexer, o string) *ParseError {
|
||||
|
||||
// Get the key length and key values
|
||||
l, _ = c.Next()
|
||||
i, err := strconv.ParseUint(l.token, 10, 8)
|
||||
if err != nil || l.err {
|
||||
i, e := strconv.ParseUint(l.token, 10, 8)
|
||||
if e != nil || l.err {
|
||||
return &ParseError{"", "bad TKEY key length", l}
|
||||
}
|
||||
rr.KeySize = uint16(i)
|
||||
@ -1682,8 +1662,8 @@ func (rr *TKEY) parse(c *zlexer, o string) *ParseError {
|
||||
|
||||
// Get the otherdata length and string data
|
||||
l, _ = c.Next()
|
||||
i, err = strconv.ParseUint(l.token, 10, 8)
|
||||
if err != nil || l.err {
|
||||
i, e1 := strconv.ParseUint(l.token, 10, 8)
|
||||
if e1 != nil || l.err {
|
||||
return &ParseError{"", "bad TKEY otherdata length", l}
|
||||
}
|
||||
rr.OtherLen = uint16(i)
|
||||
@ -1693,7 +1673,6 @@ func (rr *TKEY) parse(c *zlexer, o string) *ParseError {
|
||||
return &ParseError{"", "bad TKEY otherday", l}
|
||||
}
|
||||
rr.OtherData = l.token
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -1727,9 +1706,9 @@ func (rr *APL) parse(c *zlexer, o string) *ParseError {
|
||||
family = family[1:]
|
||||
}
|
||||
|
||||
afi, err := strconv.ParseUint(family, 10, 16)
|
||||
if err != nil {
|
||||
return &ParseError{"", "failed to parse APL family: " + err.Error(), l}
|
||||
afi, e := strconv.ParseUint(family, 10, 16)
|
||||
if e != nil {
|
||||
return &ParseError{"", "failed to parse APL family: " + e.Error(), l}
|
||||
}
|
||||
var addrLen int
|
||||
switch afi {
|
||||
@ -1741,9 +1720,9 @@ func (rr *APL) parse(c *zlexer, o string) *ParseError {
|
||||
return &ParseError{"", "unrecognized APL family", l}
|
||||
}
|
||||
|
||||
ip, subnet, err := net.ParseCIDR(cidr)
|
||||
if err != nil {
|
||||
return &ParseError{"", "failed to parse APL address: " + err.Error(), l}
|
||||
ip, subnet, e1 := net.ParseCIDR(cidr)
|
||||
if e1 != nil {
|
||||
return &ParseError{"", "failed to parse APL address: " + e1.Error(), l}
|
||||
}
|
||||
if !ip.Equal(subnet.IP) {
|
||||
return &ParseError{"", "extra bits in APL address", l}
|
||||
|
7
vendor/github.com/miekg/dns/serve_mux.go
generated
vendored
7
vendor/github.com/miekg/dns/serve_mux.go
generated
vendored
@ -1,7 +1,6 @@
|
||||
package dns
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
@ -36,7 +35,7 @@ func (mux *ServeMux) match(q string, t uint16) Handler {
|
||||
return nil
|
||||
}
|
||||
|
||||
q = strings.ToLower(q)
|
||||
q = CanonicalName(q)
|
||||
|
||||
var handler Handler
|
||||
for off, end := 0, false; !end; off, end = NextLabel(q, off) {
|
||||
@ -66,7 +65,7 @@ func (mux *ServeMux) Handle(pattern string, handler Handler) {
|
||||
if mux.z == nil {
|
||||
mux.z = make(map[string]Handler)
|
||||
}
|
||||
mux.z[Fqdn(pattern)] = handler
|
||||
mux.z[CanonicalName(pattern)] = handler
|
||||
mux.m.Unlock()
|
||||
}
|
||||
|
||||
@ -81,7 +80,7 @@ func (mux *ServeMux) HandleRemove(pattern string) {
|
||||
panic("dns: invalid pattern " + pattern)
|
||||
}
|
||||
mux.m.Lock()
|
||||
delete(mux.z, Fqdn(pattern))
|
||||
delete(mux.z, CanonicalName(pattern))
|
||||
mux.m.Unlock()
|
||||
}
|
||||
|
||||
|
8
vendor/github.com/miekg/dns/tsig.go
generated
vendored
8
vendor/github.com/miekg/dns/tsig.go
generated
vendored
@ -115,7 +115,7 @@ func TsigGenerate(m *Msg, secret, requestMAC string, timersOnly bool) ([]byte, s
|
||||
|
||||
t := new(TSIG)
|
||||
var h hash.Hash
|
||||
switch strings.ToLower(rr.Algorithm) {
|
||||
switch CanonicalName(rr.Algorithm) {
|
||||
case HmacMD5:
|
||||
h = hmac.New(md5.New, rawsecret)
|
||||
case HmacSHA1:
|
||||
@ -182,7 +182,7 @@ func TsigVerify(msg []byte, secret, requestMAC string, timersOnly bool) error {
|
||||
}
|
||||
|
||||
var h hash.Hash
|
||||
switch strings.ToLower(tsig.Algorithm) {
|
||||
switch CanonicalName(tsig.Algorithm) {
|
||||
case HmacMD5:
|
||||
h = hmac.New(md5.New, rawsecret)
|
||||
case HmacSHA1:
|
||||
@ -232,10 +232,10 @@ func tsigBuffer(msgbuf []byte, rr *TSIG, requestMAC string, timersOnly bool) []b
|
||||
tsigvar = tsigvar[:n]
|
||||
} else {
|
||||
tsig := new(tsigWireFmt)
|
||||
tsig.Name = strings.ToLower(rr.Hdr.Name)
|
||||
tsig.Name = CanonicalName(rr.Hdr.Name)
|
||||
tsig.Class = ClassANY
|
||||
tsig.Ttl = rr.Hdr.Ttl
|
||||
tsig.Algorithm = strings.ToLower(rr.Algorithm)
|
||||
tsig.Algorithm = CanonicalName(rr.Algorithm)
|
||||
tsig.TimeSigned = rr.TimeSigned
|
||||
tsig.Fudge = rr.Fudge
|
||||
tsig.Error = rr.Error
|
||||
|
82
vendor/github.com/miekg/dns/types.go
generated
vendored
82
vendor/github.com/miekg/dns/types.go
generated
vendored
@ -165,11 +165,11 @@ const (
|
||||
_RD = 1 << 8 // recursion desired
|
||||
_RA = 1 << 7 // recursion available
|
||||
_Z = 1 << 6 // Z
|
||||
_AD = 1 << 5 // authticated data
|
||||
_AD = 1 << 5 // authenticated data
|
||||
_CD = 1 << 4 // checking disabled
|
||||
)
|
||||
|
||||
// Various constants used in the LOC RR, See RFC 1887.
|
||||
// Various constants used in the LOC RR. See RFC 1887.
|
||||
const (
|
||||
LOC_EQUATOR = 1 << 31 // RFC 1876, Section 2.
|
||||
LOC_PRIMEMERIDIAN = 1 << 31 // RFC 1876, Section 2.
|
||||
@ -209,8 +209,11 @@ var CertTypeToString = map[uint16]string{
|
||||
|
||||
//go:generate go run types_generate.go
|
||||
|
||||
// Question holds a DNS question. There can be multiple questions in the
|
||||
// question section of a message. Usually there is just one.
|
||||
// Question holds a DNS question. Usually there is just one. While the
|
||||
// original DNS RFCs allow multiple questions in the question section of a
|
||||
// message, in practice it never works. Because most DNS servers see multiple
|
||||
// questions as an error, it is recommended to only have one question per
|
||||
// message.
|
||||
type Question struct {
|
||||
Name string `dns:"cdomain-name"` // "cdomain-name" specifies encoding (and may be compressed)
|
||||
Qtype uint16
|
||||
@ -231,7 +234,7 @@ func (q *Question) String() (s string) {
|
||||
return s
|
||||
}
|
||||
|
||||
// ANY is a wildcard record. See RFC 1035, Section 3.2.3. ANY
|
||||
// ANY is a wild card record. See RFC 1035, Section 3.2.3. ANY
|
||||
// is named "*" there.
|
||||
type ANY struct {
|
||||
Hdr RR_Header
|
||||
@ -442,45 +445,38 @@ func sprintName(s string) string {
|
||||
var dst strings.Builder
|
||||
|
||||
for i := 0; i < len(s); {
|
||||
if i+1 < len(s) && s[i] == '\\' && s[i+1] == '.' {
|
||||
if s[i] == '.' {
|
||||
if dst.Len() != 0 {
|
||||
dst.WriteString(s[i : i+2])
|
||||
dst.WriteByte('.')
|
||||
}
|
||||
i += 2
|
||||
i++
|
||||
continue
|
||||
}
|
||||
|
||||
b, n := nextByte(s, i)
|
||||
if n == 0 {
|
||||
i++
|
||||
continue
|
||||
}
|
||||
if b == '.' {
|
||||
if dst.Len() != 0 {
|
||||
dst.WriteByte('.')
|
||||
// Drop "dangling" incomplete escapes.
|
||||
if dst.Len() == 0 {
|
||||
return s[:i]
|
||||
}
|
||||
i += n
|
||||
continue
|
||||
break
|
||||
}
|
||||
switch b {
|
||||
case ' ', '\'', '@', ';', '(', ')', '"', '\\': // additional chars to escape
|
||||
if isDomainNameLabelSpecial(b) {
|
||||
if dst.Len() == 0 {
|
||||
dst.Grow(len(s) * 2)
|
||||
dst.WriteString(s[:i])
|
||||
}
|
||||
dst.WriteByte('\\')
|
||||
dst.WriteByte(b)
|
||||
default:
|
||||
if ' ' <= b && b <= '~' {
|
||||
if dst.Len() != 0 {
|
||||
dst.WriteByte(b)
|
||||
}
|
||||
} else {
|
||||
if dst.Len() == 0 {
|
||||
dst.Grow(len(s) * 2)
|
||||
dst.WriteString(s[:i])
|
||||
}
|
||||
dst.WriteString(escapeByte(b))
|
||||
} else if b < ' ' || b > '~' { // unprintable, use \DDD
|
||||
if dst.Len() == 0 {
|
||||
dst.Grow(len(s) * 2)
|
||||
dst.WriteString(s[:i])
|
||||
}
|
||||
dst.WriteString(escapeByte(b))
|
||||
} else {
|
||||
if dst.Len() != 0 {
|
||||
dst.WriteByte(b)
|
||||
}
|
||||
}
|
||||
i += n
|
||||
@ -503,15 +499,10 @@ func sprintTxtOctet(s string) string {
|
||||
}
|
||||
|
||||
b, n := nextByte(s, i)
|
||||
switch {
|
||||
case n == 0:
|
||||
if n == 0 {
|
||||
i++ // dangling back slash
|
||||
case b == '.':
|
||||
dst.WriteByte('.')
|
||||
case b < ' ' || b > '~':
|
||||
dst.WriteString(escapeByte(b))
|
||||
default:
|
||||
dst.WriteByte(b)
|
||||
} else {
|
||||
writeTXTStringByte(&dst, b)
|
||||
}
|
||||
i += n
|
||||
}
|
||||
@ -587,6 +578,17 @@ func escapeByte(b byte) string {
|
||||
return escapedByteLarge[int(b)*4 : int(b)*4+4]
|
||||
}
|
||||
|
||||
// isDomainNameLabelSpecial returns true if
|
||||
// a domain name label byte should be prefixed
|
||||
// with an escaping backslash.
|
||||
func isDomainNameLabelSpecial(b byte) bool {
|
||||
switch b {
|
||||
case '.', ' ', '\'', '@', ';', '(', ')', '"', '\\':
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func nextByte(s string, offset int) (byte, int) {
|
||||
if offset >= len(s) {
|
||||
return 0, 0
|
||||
@ -759,8 +761,8 @@ type LOC struct {
|
||||
Altitude uint32
|
||||
}
|
||||
|
||||
// cmToM takes a cm value expressed in RFC1876 SIZE mantissa/exponent
|
||||
// format and returns a string in m (two decimals for the cm)
|
||||
// cmToM takes a cm value expressed in RFC 1876 SIZE mantissa/exponent
|
||||
// format and returns a string in m (two decimals for the cm).
|
||||
func cmToM(m, e uint8) string {
|
||||
if e < 2 {
|
||||
if e == 1 {
|
||||
@ -1118,6 +1120,7 @@ type URI struct {
|
||||
Target string `dns:"octet"`
|
||||
}
|
||||
|
||||
// rr.Target to be parsed as a sequence of character encoded octets according to RFC 3986
|
||||
func (rr *URI) String() string {
|
||||
return rr.Hdr.String() + strconv.Itoa(int(rr.Priority)) +
|
||||
" " + strconv.Itoa(int(rr.Weight)) + " " + sprintTxtOctet(rr.Target)
|
||||
@ -1279,6 +1282,7 @@ type CAA struct {
|
||||
Value string `dns:"octet"`
|
||||
}
|
||||
|
||||
// rr.Value Is the character-string encoding of the value field as specified in RFC 1035, Section 5.1.
|
||||
func (rr *CAA) String() string {
|
||||
return rr.Hdr.String() + strconv.Itoa(int(rr.Flag)) + " " + rr.Tag + " " + sprintTxtOctet(rr.Value)
|
||||
}
|
||||
|
8
vendor/github.com/miekg/dns/version.go
generated
vendored
8
vendor/github.com/miekg/dns/version.go
generated
vendored
@ -3,13 +3,13 @@ package dns
|
||||
import "fmt"
|
||||
|
||||
// Version is current version of this library.
|
||||
var Version = V{1, 1, 27}
|
||||
var Version = v{1, 1, 30}
|
||||
|
||||
// V holds the version of this library.
|
||||
type V struct {
|
||||
// v holds the version of this library.
|
||||
type v struct {
|
||||
Major, Minor, Patch int
|
||||
}
|
||||
|
||||
func (v V) String() string {
|
||||
func (v v) String() string {
|
||||
return fmt.Sprintf("%d.%d.%d", v.Major, v.Minor, v.Patch)
|
||||
}
|
||||
|
120
vendor/github.com/miekg/dns/zduplicate.go
generated
vendored
120
vendor/github.com/miekg/dns/zduplicate.go
generated
vendored
@ -104,6 +104,48 @@ func (r1 *CAA) isDuplicate(_r2 RR) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (r1 *CDNSKEY) isDuplicate(_r2 RR) bool {
|
||||
r2, ok := _r2.(*CDNSKEY)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
_ = r2
|
||||
if r1.Flags != r2.Flags {
|
||||
return false
|
||||
}
|
||||
if r1.Protocol != r2.Protocol {
|
||||
return false
|
||||
}
|
||||
if r1.Algorithm != r2.Algorithm {
|
||||
return false
|
||||
}
|
||||
if r1.PublicKey != r2.PublicKey {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (r1 *CDS) isDuplicate(_r2 RR) bool {
|
||||
r2, ok := _r2.(*CDS)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
_ = r2
|
||||
if r1.KeyTag != r2.KeyTag {
|
||||
return false
|
||||
}
|
||||
if r1.Algorithm != r2.Algorithm {
|
||||
return false
|
||||
}
|
||||
if r1.DigestType != r2.DigestType {
|
||||
return false
|
||||
}
|
||||
if r1.Digest != r2.Digest {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (r1 *CERT) isDuplicate(_r2 RR) bool {
|
||||
r2, ok := _r2.(*CERT)
|
||||
if !ok {
|
||||
@ -172,6 +214,27 @@ func (r1 *DHCID) isDuplicate(_r2 RR) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (r1 *DLV) isDuplicate(_r2 RR) bool {
|
||||
r2, ok := _r2.(*DLV)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
_ = r2
|
||||
if r1.KeyTag != r2.KeyTag {
|
||||
return false
|
||||
}
|
||||
if r1.Algorithm != r2.Algorithm {
|
||||
return false
|
||||
}
|
||||
if r1.DigestType != r2.DigestType {
|
||||
return false
|
||||
}
|
||||
if r1.Digest != r2.Digest {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (r1 *DNAME) isDuplicate(_r2 RR) bool {
|
||||
r2, ok := _r2.(*DNAME)
|
||||
if !ok {
|
||||
@ -339,6 +402,27 @@ func (r1 *HIP) isDuplicate(_r2 RR) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (r1 *KEY) isDuplicate(_r2 RR) bool {
|
||||
r2, ok := _r2.(*KEY)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
_ = r2
|
||||
if r1.Flags != r2.Flags {
|
||||
return false
|
||||
}
|
||||
if r1.Protocol != r2.Protocol {
|
||||
return false
|
||||
}
|
||||
if r1.Algorithm != r2.Algorithm {
|
||||
return false
|
||||
}
|
||||
if r1.PublicKey != r2.PublicKey {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (r1 *KX) isDuplicate(_r2 RR) bool {
|
||||
r2, ok := _r2.(*KX)
|
||||
if !ok {
|
||||
@ -849,6 +933,42 @@ func (r1 *RT) isDuplicate(_r2 RR) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (r1 *SIG) isDuplicate(_r2 RR) bool {
|
||||
r2, ok := _r2.(*SIG)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
_ = r2
|
||||
if r1.TypeCovered != r2.TypeCovered {
|
||||
return false
|
||||
}
|
||||
if r1.Algorithm != r2.Algorithm {
|
||||
return false
|
||||
}
|
||||
if r1.Labels != r2.Labels {
|
||||
return false
|
||||
}
|
||||
if r1.OrigTtl != r2.OrigTtl {
|
||||
return false
|
||||
}
|
||||
if r1.Expiration != r2.Expiration {
|
||||
return false
|
||||
}
|
||||
if r1.Inception != r2.Inception {
|
||||
return false
|
||||
}
|
||||
if r1.KeyTag != r2.KeyTag {
|
||||
return false
|
||||
}
|
||||
if !isDuplicateName(r1.SignerName, r2.SignerName) {
|
||||
return false
|
||||
}
|
||||
if r1.Signature != r2.Signature {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (r1 *SMIMEA) isDuplicate(_r2 RR) bool {
|
||||
r2, ok := _r2.(*SMIMEA)
|
||||
if !ok {
|
||||
|
19
vendor/github.com/miekg/dns/ztypes.go
generated
vendored
19
vendor/github.com/miekg/dns/ztypes.go
generated
vendored
@ -685,8 +685,8 @@ func (rr *ANY) copy() RR {
|
||||
}
|
||||
func (rr *APL) copy() RR {
|
||||
Prefixes := make([]APLPrefix, len(rr.Prefixes))
|
||||
for i := range rr.Prefixes {
|
||||
Prefixes[i] = rr.Prefixes[i].copy()
|
||||
for i, e := range rr.Prefixes {
|
||||
Prefixes[i] = e.copy()
|
||||
}
|
||||
return &APL{rr.Hdr, Prefixes}
|
||||
}
|
||||
@ -698,6 +698,12 @@ func (rr *AVC) copy() RR {
|
||||
func (rr *CAA) copy() RR {
|
||||
return &CAA{rr.Hdr, rr.Flag, rr.Tag, rr.Value}
|
||||
}
|
||||
func (rr *CDNSKEY) copy() RR {
|
||||
return &CDNSKEY{*rr.DNSKEY.copy().(*DNSKEY)}
|
||||
}
|
||||
func (rr *CDS) copy() RR {
|
||||
return &CDS{*rr.DS.copy().(*DS)}
|
||||
}
|
||||
func (rr *CERT) copy() RR {
|
||||
return &CERT{rr.Hdr, rr.Type, rr.KeyTag, rr.Algorithm, rr.Certificate}
|
||||
}
|
||||
@ -712,6 +718,9 @@ func (rr *CSYNC) copy() RR {
|
||||
func (rr *DHCID) copy() RR {
|
||||
return &DHCID{rr.Hdr, rr.Digest}
|
||||
}
|
||||
func (rr *DLV) copy() RR {
|
||||
return &DLV{*rr.DS.copy().(*DS)}
|
||||
}
|
||||
func (rr *DNAME) copy() RR {
|
||||
return &DNAME{rr.Hdr, rr.Target}
|
||||
}
|
||||
@ -744,6 +753,9 @@ func (rr *HIP) copy() RR {
|
||||
copy(RendezvousServers, rr.RendezvousServers)
|
||||
return &HIP{rr.Hdr, rr.HitLength, rr.PublicKeyAlgorithm, rr.PublicKeyLength, rr.Hit, rr.PublicKey, RendezvousServers}
|
||||
}
|
||||
func (rr *KEY) copy() RR {
|
||||
return &KEY{*rr.DNSKEY.copy().(*DNSKEY)}
|
||||
}
|
||||
func (rr *KX) copy() RR {
|
||||
return &KX{rr.Hdr, rr.Preference, rr.Exchanger}
|
||||
}
|
||||
@ -847,6 +859,9 @@ func (rr *RRSIG) copy() RR {
|
||||
func (rr *RT) copy() RR {
|
||||
return &RT{rr.Hdr, rr.Preference, rr.Host}
|
||||
}
|
||||
func (rr *SIG) copy() RR {
|
||||
return &SIG{*rr.RRSIG.copy().(*RRSIG)}
|
||||
}
|
||||
func (rr *SMIMEA) copy() RR {
|
||||
return &SMIMEA{rr.Hdr, rr.Usage, rr.Selector, rr.MatchingType, rr.Certificate}
|
||||
}
|
||||
|
15
vendor/go.uber.org/atomic/.codecov.yml
generated
vendored
Normal file
15
vendor/go.uber.org/atomic/.codecov.yml
generated
vendored
Normal file
@ -0,0 +1,15 @@
|
||||
coverage:
|
||||
range: 80..100
|
||||
round: down
|
||||
precision: 2
|
||||
|
||||
status:
|
||||
project: # measuring the overall project coverage
|
||||
default: # context, you can create multiple ones with custom titles
|
||||
enabled: yes # must be yes|true to enable this status
|
||||
target: 100 # specify the target coverage for each commit status
|
||||
# option: "auto" (must increase from parent commit or pull request base)
|
||||
# option: "X%" a static target percentage to hit
|
||||
if_not_found: success # if parent is not found report status as success, error, or failure
|
||||
if_ci_failed: error # if ci fails report status as success, error, or failure
|
||||
|
12
vendor/go.uber.org/atomic/.gitignore
generated
vendored
Normal file
12
vendor/go.uber.org/atomic/.gitignore
generated
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
/bin
|
||||
.DS_Store
|
||||
/vendor
|
||||
cover.html
|
||||
cover.out
|
||||
lint.log
|
||||
|
||||
# Binaries
|
||||
*.test
|
||||
|
||||
# Profiling output
|
||||
*.prof
|
27
vendor/go.uber.org/atomic/.travis.yml
generated
vendored
Normal file
27
vendor/go.uber.org/atomic/.travis.yml
generated
vendored
Normal file
@ -0,0 +1,27 @@
|
||||
sudo: false
|
||||
language: go
|
||||
go_import_path: go.uber.org/atomic
|
||||
|
||||
env:
|
||||
global:
|
||||
- GO111MODULE=on
|
||||
|
||||
matrix:
|
||||
include:
|
||||
- go: 1.12.x
|
||||
- go: 1.13.x
|
||||
env: LINT=1
|
||||
|
||||
cache:
|
||||
directories:
|
||||
- vendor
|
||||
|
||||
before_install:
|
||||
- go version
|
||||
|
||||
script:
|
||||
- test -z "$LINT" || make lint
|
||||
- make cover
|
||||
|
||||
after_success:
|
||||
- bash <(curl -s https://codecov.io/bash)
|
64
vendor/go.uber.org/atomic/CHANGELOG.md
generated
vendored
Normal file
64
vendor/go.uber.org/atomic/CHANGELOG.md
generated
vendored
Normal file
@ -0,0 +1,64 @@
|
||||
# Changelog
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [1.6.0] - 2020-02-24
|
||||
### Changed
|
||||
- Drop library dependency on `golang.org/x/{lint, tools}`.
|
||||
|
||||
## [1.5.1] - 2019-11-19
|
||||
- Fix bug where `Bool.CAS` and `Bool.Toggle` do work correctly together
|
||||
causing `CAS` to fail even though the old value matches.
|
||||
|
||||
## [1.5.0] - 2019-10-29
|
||||
### Changed
|
||||
- With Go modules, only the `go.uber.org/atomic` import path is supported now.
|
||||
If you need to use the old import path, please add a `replace` directive to
|
||||
your `go.mod`.
|
||||
|
||||
## [1.4.0] - 2019-05-01
|
||||
### Added
|
||||
- Add `atomic.Error` type for atomic operations on `error` values.
|
||||
|
||||
## [1.3.2] - 2018-05-02
|
||||
### Added
|
||||
- Add `atomic.Duration` type for atomic operations on `time.Duration` values.
|
||||
|
||||
## [1.3.1] - 2017-11-14
|
||||
### Fixed
|
||||
- Revert optimization for `atomic.String.Store("")` which caused data races.
|
||||
|
||||
## [1.3.0] - 2017-11-13
|
||||
### Added
|
||||
- Add `atomic.Bool.CAS` for compare-and-swap semantics on bools.
|
||||
|
||||
### Changed
|
||||
- Optimize `atomic.String.Store("")` by avoiding an allocation.
|
||||
|
||||
## [1.2.0] - 2017-04-12
|
||||
### Added
|
||||
- Shadow `atomic.Value` from `sync/atomic`.
|
||||
|
||||
## [1.1.0] - 2017-03-10
|
||||
### Added
|
||||
- Add atomic `Float64` type.
|
||||
|
||||
### Changed
|
||||
- Support new `go.uber.org/atomic` import path.
|
||||
|
||||
## [1.0.0] - 2016-07-18
|
||||
|
||||
- Initial release.
|
||||
|
||||
[1.6.0]: https://github.com/uber-go/atomic/compare/v1.5.1...v1.6.0
|
||||
[1.5.1]: https://github.com/uber-go/atomic/compare/v1.5.0...v1.5.1
|
||||
[1.5.0]: https://github.com/uber-go/atomic/compare/v1.4.0...v1.5.0
|
||||
[1.4.0]: https://github.com/uber-go/atomic/compare/v1.3.2...v1.4.0
|
||||
[1.3.2]: https://github.com/uber-go/atomic/compare/v1.3.1...v1.3.2
|
||||
[1.3.1]: https://github.com/uber-go/atomic/compare/v1.3.0...v1.3.1
|
||||
[1.3.0]: https://github.com/uber-go/atomic/compare/v1.2.0...v1.3.0
|
||||
[1.2.0]: https://github.com/uber-go/atomic/compare/v1.1.0...v1.2.0
|
||||
[1.1.0]: https://github.com/uber-go/atomic/compare/v1.0.0...v1.1.0
|
||||
[1.0.0]: https://github.com/uber-go/atomic/releases/tag/v1.0.0
|
19
vendor/go.uber.org/atomic/LICENSE.txt
generated
vendored
Normal file
19
vendor/go.uber.org/atomic/LICENSE.txt
generated
vendored
Normal file
@ -0,0 +1,19 @@
|
||||
Copyright (c) 2016 Uber Technologies, Inc.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
35
vendor/go.uber.org/atomic/Makefile
generated
vendored
Normal file
35
vendor/go.uber.org/atomic/Makefile
generated
vendored
Normal file
@ -0,0 +1,35 @@
|
||||
# Directory to place `go install`ed binaries into.
|
||||
export GOBIN ?= $(shell pwd)/bin
|
||||
|
||||
GOLINT = $(GOBIN)/golint
|
||||
|
||||
GO_FILES ?= *.go
|
||||
|
||||
.PHONY: build
|
||||
build:
|
||||
go build ./...
|
||||
|
||||
.PHONY: test
|
||||
test:
|
||||
go test -race ./...
|
||||
|
||||
.PHONY: gofmt
|
||||
gofmt:
|
||||
$(eval FMT_LOG := $(shell mktemp -t gofmt.XXXXX))
|
||||
gofmt -e -s -l $(GO_FILES) > $(FMT_LOG) || true
|
||||
@[ ! -s "$(FMT_LOG)" ] || (echo "gofmt failed:" && cat $(FMT_LOG) && false)
|
||||
|
||||
$(GOLINT):
|
||||
go install golang.org/x/lint/golint
|
||||
|
||||
.PHONY: golint
|
||||
golint: $(GOLINT)
|
||||
$(GOLINT) ./...
|
||||
|
||||
.PHONY: lint
|
||||
lint: gofmt golint
|
||||
|
||||
.PHONY: cover
|
||||
cover:
|
||||
go test -coverprofile=cover.out -coverpkg ./... -v ./...
|
||||
go tool cover -html=cover.out -o cover.html
|
63
vendor/go.uber.org/atomic/README.md
generated
vendored
Normal file
63
vendor/go.uber.org/atomic/README.md
generated
vendored
Normal file
@ -0,0 +1,63 @@
|
||||
# atomic [![GoDoc][doc-img]][doc] [![Build Status][ci-img]][ci] [![Coverage Status][cov-img]][cov] [![Go Report Card][reportcard-img]][reportcard]
|
||||
|
||||
Simple wrappers for primitive types to enforce atomic access.
|
||||
|
||||
## Installation
|
||||
|
||||
```shell
|
||||
$ go get -u go.uber.org/atomic@v1
|
||||
```
|
||||
|
||||
### Legacy Import Path
|
||||
|
||||
As of v1.5.0, the import path `go.uber.org/atomic` is the only supported way
|
||||
of using this package. If you are using Go modules, this package will fail to
|
||||
compile with the legacy import path path `github.com/uber-go/atomic`.
|
||||
|
||||
We recommend migrating your code to the new import path but if you're unable
|
||||
to do so, or if your dependencies are still using the old import path, you
|
||||
will have to add a `replace` directive to your `go.mod` file downgrading the
|
||||
legacy import path to an older version.
|
||||
|
||||
```
|
||||
replace github.com/uber-go/atomic => github.com/uber-go/atomic v1.4.0
|
||||
```
|
||||
|
||||
You can do so automatically by running the following command.
|
||||
|
||||
```shell
|
||||
$ go mod edit -replace github.com/uber-go/atomic=github.com/uber-go/atomic@v1.4.0
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
The standard library's `sync/atomic` is powerful, but it's easy to forget which
|
||||
variables must be accessed atomically. `go.uber.org/atomic` preserves all the
|
||||
functionality of the standard library, but wraps the primitive types to
|
||||
provide a safer, more convenient API.
|
||||
|
||||
```go
|
||||
var atom atomic.Uint32
|
||||
atom.Store(42)
|
||||
atom.Sub(2)
|
||||
atom.CAS(40, 11)
|
||||
```
|
||||
|
||||
See the [documentation][doc] for a complete API specification.
|
||||
|
||||
## Development Status
|
||||
|
||||
Stable.
|
||||
|
||||
---
|
||||
|
||||
Released under the [MIT License](LICENSE.txt).
|
||||
|
||||
[doc-img]: https://godoc.org/github.com/uber-go/atomic?status.svg
|
||||
[doc]: https://godoc.org/go.uber.org/atomic
|
||||
[ci-img]: https://travis-ci.com/uber-go/atomic.svg?branch=master
|
||||
[ci]: https://travis-ci.com/uber-go/atomic
|
||||
[cov-img]: https://codecov.io/gh/uber-go/atomic/branch/master/graph/badge.svg
|
||||
[cov]: https://codecov.io/gh/uber-go/atomic
|
||||
[reportcard-img]: https://goreportcard.com/badge/go.uber.org/atomic
|
||||
[reportcard]: https://goreportcard.com/report/go.uber.org/atomic
|
356
vendor/go.uber.org/atomic/atomic.go
generated
vendored
Normal file
356
vendor/go.uber.org/atomic/atomic.go
generated
vendored
Normal file
@ -0,0 +1,356 @@
|
||||
// Copyright (c) 2016 Uber Technologies, Inc.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
|
||||
// Package atomic provides simple wrappers around numerics to enforce atomic
|
||||
// access.
|
||||
package atomic
|
||||
|
||||
import (
|
||||
"math"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Int32 is an atomic wrapper around an int32.
|
||||
type Int32 struct{ v int32 }
|
||||
|
||||
// NewInt32 creates an Int32.
|
||||
func NewInt32(i int32) *Int32 {
|
||||
return &Int32{i}
|
||||
}
|
||||
|
||||
// Load atomically loads the wrapped value.
|
||||
func (i *Int32) Load() int32 {
|
||||
return atomic.LoadInt32(&i.v)
|
||||
}
|
||||
|
||||
// Add atomically adds to the wrapped int32 and returns the new value.
|
||||
func (i *Int32) Add(n int32) int32 {
|
||||
return atomic.AddInt32(&i.v, n)
|
||||
}
|
||||
|
||||
// Sub atomically subtracts from the wrapped int32 and returns the new value.
|
||||
func (i *Int32) Sub(n int32) int32 {
|
||||
return atomic.AddInt32(&i.v, -n)
|
||||
}
|
||||
|
||||
// Inc atomically increments the wrapped int32 and returns the new value.
|
||||
func (i *Int32) Inc() int32 {
|
||||
return i.Add(1)
|
||||
}
|
||||
|
||||
// Dec atomically decrements the wrapped int32 and returns the new value.
|
||||
func (i *Int32) Dec() int32 {
|
||||
return i.Sub(1)
|
||||
}
|
||||
|
||||
// CAS is an atomic compare-and-swap.
|
||||
func (i *Int32) CAS(old, new int32) bool {
|
||||
return atomic.CompareAndSwapInt32(&i.v, old, new)
|
||||
}
|
||||
|
||||
// Store atomically stores the passed value.
|
||||
func (i *Int32) Store(n int32) {
|
||||
atomic.StoreInt32(&i.v, n)
|
||||
}
|
||||
|
||||
// Swap atomically swaps the wrapped int32 and returns the old value.
|
||||
func (i *Int32) Swap(n int32) int32 {
|
||||
return atomic.SwapInt32(&i.v, n)
|
||||
}
|
||||
|
||||
// Int64 is an atomic wrapper around an int64.
|
||||
type Int64 struct{ v int64 }
|
||||
|
||||
// NewInt64 creates an Int64.
|
||||
func NewInt64(i int64) *Int64 {
|
||||
return &Int64{i}
|
||||
}
|
||||
|
||||
// Load atomically loads the wrapped value.
|
||||
func (i *Int64) Load() int64 {
|
||||
return atomic.LoadInt64(&i.v)
|
||||
}
|
||||
|
||||
// Add atomically adds to the wrapped int64 and returns the new value.
|
||||
func (i *Int64) Add(n int64) int64 {
|
||||
return atomic.AddInt64(&i.v, n)
|
||||
}
|
||||
|
||||
// Sub atomically subtracts from the wrapped int64 and returns the new value.
|
||||
func (i *Int64) Sub(n int64) int64 {
|
||||
return atomic.AddInt64(&i.v, -n)
|
||||
}
|
||||
|
||||
// Inc atomically increments the wrapped int64 and returns the new value.
|
||||
func (i *Int64) Inc() int64 {
|
||||
return i.Add(1)
|
||||
}
|
||||
|
||||
// Dec atomically decrements the wrapped int64 and returns the new value.
|
||||
func (i *Int64) Dec() int64 {
|
||||
return i.Sub(1)
|
||||
}
|
||||
|
||||
// CAS is an atomic compare-and-swap.
|
||||
func (i *Int64) CAS(old, new int64) bool {
|
||||
return atomic.CompareAndSwapInt64(&i.v, old, new)
|
||||
}
|
||||
|
||||
// Store atomically stores the passed value.
|
||||
func (i *Int64) Store(n int64) {
|
||||
atomic.StoreInt64(&i.v, n)
|
||||
}
|
||||
|
||||
// Swap atomically swaps the wrapped int64 and returns the old value.
|
||||
func (i *Int64) Swap(n int64) int64 {
|
||||
return atomic.SwapInt64(&i.v, n)
|
||||
}
|
||||
|
||||
// Uint32 is an atomic wrapper around an uint32.
|
||||
type Uint32 struct{ v uint32 }
|
||||
|
||||
// NewUint32 creates a Uint32.
|
||||
func NewUint32(i uint32) *Uint32 {
|
||||
return &Uint32{i}
|
||||
}
|
||||
|
||||
// Load atomically loads the wrapped value.
|
||||
func (i *Uint32) Load() uint32 {
|
||||
return atomic.LoadUint32(&i.v)
|
||||
}
|
||||
|
||||
// Add atomically adds to the wrapped uint32 and returns the new value.
|
||||
func (i *Uint32) Add(n uint32) uint32 {
|
||||
return atomic.AddUint32(&i.v, n)
|
||||
}
|
||||
|
||||
// Sub atomically subtracts from the wrapped uint32 and returns the new value.
|
||||
func (i *Uint32) Sub(n uint32) uint32 {
|
||||
return atomic.AddUint32(&i.v, ^(n - 1))
|
||||
}
|
||||
|
||||
// Inc atomically increments the wrapped uint32 and returns the new value.
|
||||
func (i *Uint32) Inc() uint32 {
|
||||
return i.Add(1)
|
||||
}
|
||||
|
||||
// Dec atomically decrements the wrapped int32 and returns the new value.
|
||||
func (i *Uint32) Dec() uint32 {
|
||||
return i.Sub(1)
|
||||
}
|
||||
|
||||
// CAS is an atomic compare-and-swap.
|
||||
func (i *Uint32) CAS(old, new uint32) bool {
|
||||
return atomic.CompareAndSwapUint32(&i.v, old, new)
|
||||
}
|
||||
|
||||
// Store atomically stores the passed value.
|
||||
func (i *Uint32) Store(n uint32) {
|
||||
atomic.StoreUint32(&i.v, n)
|
||||
}
|
||||
|
||||
// Swap atomically swaps the wrapped uint32 and returns the old value.
|
||||
func (i *Uint32) Swap(n uint32) uint32 {
|
||||
return atomic.SwapUint32(&i.v, n)
|
||||
}
|
||||
|
||||
// Uint64 is an atomic wrapper around a uint64.
|
||||
type Uint64 struct{ v uint64 }
|
||||
|
||||
// NewUint64 creates a Uint64.
|
||||
func NewUint64(i uint64) *Uint64 {
|
||||
return &Uint64{i}
|
||||
}
|
||||
|
||||
// Load atomically loads the wrapped value.
|
||||
func (i *Uint64) Load() uint64 {
|
||||
return atomic.LoadUint64(&i.v)
|
||||
}
|
||||
|
||||
// Add atomically adds to the wrapped uint64 and returns the new value.
|
||||
func (i *Uint64) Add(n uint64) uint64 {
|
||||
return atomic.AddUint64(&i.v, n)
|
||||
}
|
||||
|
||||
// Sub atomically subtracts from the wrapped uint64 and returns the new value.
|
||||
func (i *Uint64) Sub(n uint64) uint64 {
|
||||
return atomic.AddUint64(&i.v, ^(n - 1))
|
||||
}
|
||||
|
||||
// Inc atomically increments the wrapped uint64 and returns the new value.
|
||||
func (i *Uint64) Inc() uint64 {
|
||||
return i.Add(1)
|
||||
}
|
||||
|
||||
// Dec atomically decrements the wrapped uint64 and returns the new value.
|
||||
func (i *Uint64) Dec() uint64 {
|
||||
return i.Sub(1)
|
||||
}
|
||||
|
||||
// CAS is an atomic compare-and-swap.
|
||||
func (i *Uint64) CAS(old, new uint64) bool {
|
||||
return atomic.CompareAndSwapUint64(&i.v, old, new)
|
||||
}
|
||||
|
||||
// Store atomically stores the passed value.
|
||||
func (i *Uint64) Store(n uint64) {
|
||||
atomic.StoreUint64(&i.v, n)
|
||||
}
|
||||
|
||||
// Swap atomically swaps the wrapped uint64 and returns the old value.
|
||||
func (i *Uint64) Swap(n uint64) uint64 {
|
||||
return atomic.SwapUint64(&i.v, n)
|
||||
}
|
||||
|
||||
// Bool is an atomic Boolean.
|
||||
type Bool struct{ v uint32 }
|
||||
|
||||
// NewBool creates a Bool.
|
||||
func NewBool(initial bool) *Bool {
|
||||
return &Bool{boolToInt(initial)}
|
||||
}
|
||||
|
||||
// Load atomically loads the Boolean.
|
||||
func (b *Bool) Load() bool {
|
||||
return truthy(atomic.LoadUint32(&b.v))
|
||||
}
|
||||
|
||||
// CAS is an atomic compare-and-swap.
|
||||
func (b *Bool) CAS(old, new bool) bool {
|
||||
return atomic.CompareAndSwapUint32(&b.v, boolToInt(old), boolToInt(new))
|
||||
}
|
||||
|
||||
// Store atomically stores the passed value.
|
||||
func (b *Bool) Store(new bool) {
|
||||
atomic.StoreUint32(&b.v, boolToInt(new))
|
||||
}
|
||||
|
||||
// Swap sets the given value and returns the previous value.
|
||||
func (b *Bool) Swap(new bool) bool {
|
||||
return truthy(atomic.SwapUint32(&b.v, boolToInt(new)))
|
||||
}
|
||||
|
||||
// Toggle atomically negates the Boolean and returns the previous value.
|
||||
func (b *Bool) Toggle() bool {
|
||||
for {
|
||||
old := b.Load()
|
||||
if b.CAS(old, !old) {
|
||||
return old
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func truthy(n uint32) bool {
|
||||
return n == 1
|
||||
}
|
||||
|
||||
func boolToInt(b bool) uint32 {
|
||||
if b {
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// Float64 is an atomic wrapper around float64.
|
||||
type Float64 struct {
|
||||
v uint64
|
||||
}
|
||||
|
||||
// NewFloat64 creates a Float64.
|
||||
func NewFloat64(f float64) *Float64 {
|
||||
return &Float64{math.Float64bits(f)}
|
||||
}
|
||||
|
||||
// Load atomically loads the wrapped value.
|
||||
func (f *Float64) Load() float64 {
|
||||
return math.Float64frombits(atomic.LoadUint64(&f.v))
|
||||
}
|
||||
|
||||
// Store atomically stores the passed value.
|
||||
func (f *Float64) Store(s float64) {
|
||||
atomic.StoreUint64(&f.v, math.Float64bits(s))
|
||||
}
|
||||
|
||||
// Add atomically adds to the wrapped float64 and returns the new value.
|
||||
func (f *Float64) Add(s float64) float64 {
|
||||
for {
|
||||
old := f.Load()
|
||||
new := old + s
|
||||
if f.CAS(old, new) {
|
||||
return new
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Sub atomically subtracts from the wrapped float64 and returns the new value.
|
||||
func (f *Float64) Sub(s float64) float64 {
|
||||
return f.Add(-s)
|
||||
}
|
||||
|
||||
// CAS is an atomic compare-and-swap.
|
||||
func (f *Float64) CAS(old, new float64) bool {
|
||||
return atomic.CompareAndSwapUint64(&f.v, math.Float64bits(old), math.Float64bits(new))
|
||||
}
|
||||
|
||||
// Duration is an atomic wrapper around time.Duration
|
||||
// https://godoc.org/time#Duration
|
||||
type Duration struct {
|
||||
v Int64
|
||||
}
|
||||
|
||||
// NewDuration creates a Duration.
|
||||
func NewDuration(d time.Duration) *Duration {
|
||||
return &Duration{v: *NewInt64(int64(d))}
|
||||
}
|
||||
|
||||
// Load atomically loads the wrapped value.
|
||||
func (d *Duration) Load() time.Duration {
|
||||
return time.Duration(d.v.Load())
|
||||
}
|
||||
|
||||
// Store atomically stores the passed value.
|
||||
func (d *Duration) Store(n time.Duration) {
|
||||
d.v.Store(int64(n))
|
||||
}
|
||||
|
||||
// Add atomically adds to the wrapped time.Duration and returns the new value.
|
||||
func (d *Duration) Add(n time.Duration) time.Duration {
|
||||
return time.Duration(d.v.Add(int64(n)))
|
||||
}
|
||||
|
||||
// Sub atomically subtracts from the wrapped time.Duration and returns the new value.
|
||||
func (d *Duration) Sub(n time.Duration) time.Duration {
|
||||
return time.Duration(d.v.Sub(int64(n)))
|
||||
}
|
||||
|
||||
// Swap atomically swaps the wrapped time.Duration and returns the old value.
|
||||
func (d *Duration) Swap(n time.Duration) time.Duration {
|
||||
return time.Duration(d.v.Swap(int64(n)))
|
||||
}
|
||||
|
||||
// CAS is an atomic compare-and-swap.
|
||||
func (d *Duration) CAS(old, new time.Duration) bool {
|
||||
return d.v.CAS(int64(old), int64(new))
|
||||
}
|
||||
|
||||
// Value shadows the type of the same name from sync/atomic
|
||||
// https://godoc.org/sync/atomic#Value
|
||||
type Value struct{ atomic.Value }
|
55
vendor/go.uber.org/atomic/error.go
generated
vendored
Normal file
55
vendor/go.uber.org/atomic/error.go
generated
vendored
Normal file
@ -0,0 +1,55 @@
|
||||
// Copyright (c) 2016 Uber Technologies, Inc.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
|
||||
package atomic
|
||||
|
||||
// Error is an atomic type-safe wrapper around Value for errors
|
||||
type Error struct{ v Value }
|
||||
|
||||
// errorHolder is non-nil holder for error object.
|
||||
// atomic.Value panics on saving nil object, so err object needs to be
|
||||
// wrapped with valid object first.
|
||||
type errorHolder struct{ err error }
|
||||
|
||||
// NewError creates new atomic error object
|
||||
func NewError(err error) *Error {
|
||||
e := &Error{}
|
||||
if err != nil {
|
||||
e.Store(err)
|
||||
}
|
||||
return e
|
||||
}
|
||||
|
||||
// Load atomically loads the wrapped error
|
||||
func (e *Error) Load() error {
|
||||
v := e.v.Load()
|
||||
if v == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
eh := v.(errorHolder)
|
||||
return eh.err
|
||||
}
|
||||
|
||||
// Store atomically stores error.
|
||||
// NOTE: a holder object is allocated on each Store call.
|
||||
func (e *Error) Store(err error) {
|
||||
e.v.Store(errorHolder{err: err})
|
||||
}
|
10
vendor/go.uber.org/atomic/go.mod
generated
vendored
Normal file
10
vendor/go.uber.org/atomic/go.mod
generated
vendored
Normal file
@ -0,0 +1,10 @@
|
||||
module go.uber.org/atomic
|
||||
|
||||
require (
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/stretchr/testify v1.3.0
|
||||
golang.org/x/lint v0.0.0-20190930215403-16217165b5de
|
||||
golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c // indirect
|
||||
)
|
||||
|
||||
go 1.13
|
22
vendor/go.uber.org/atomic/go.sum
generated
vendored
Normal file
22
vendor/go.uber.org/atomic/go.sum
generated
vendored
Normal file
@ -0,0 +1,22 @@
|
||||
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/lint v0.0.0-20190930215403-16217165b5de h1:5hukYrvBGR8/eNkX5mdUezrA6JiaEZDtJb9Ei+1LlBs=
|
||||
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd h1:/e+gpKk9r3dJobndpTytxS2gOy6m5uvpg+ISQoEcusQ=
|
||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c h1:IGkKhmfzcztjm6gYkykvu/NiS8kaqbCWAEWWAyf8J5U=
|
||||
golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
49
vendor/go.uber.org/atomic/string.go
generated
vendored
Normal file
49
vendor/go.uber.org/atomic/string.go
generated
vendored
Normal file
@ -0,0 +1,49 @@
|
||||
// Copyright (c) 2016 Uber Technologies, Inc.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
|
||||
package atomic
|
||||
|
||||
// String is an atomic type-safe wrapper around Value for strings.
|
||||
type String struct{ v Value }
|
||||
|
||||
// NewString creates a String.
|
||||
func NewString(str string) *String {
|
||||
s := &String{}
|
||||
if str != "" {
|
||||
s.Store(str)
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// Load atomically loads the wrapped string.
|
||||
func (s *String) Load() string {
|
||||
v := s.v.Load()
|
||||
if v == nil {
|
||||
return ""
|
||||
}
|
||||
return v.(string)
|
||||
}
|
||||
|
||||
// Store atomically stores the passed string.
|
||||
// Note: Converting the string to an interface{} to store in the Value
|
||||
// requires an allocation.
|
||||
func (s *String) Store(str string) {
|
||||
s.v.Store(str)
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user