aboutsummaryrefslogtreecommitdiff
path: root/core/https/client.go
diff options
context:
space:
mode:
Diffstat (limited to 'core/https/client.go')
-rw-r--r--core/https/client.go215
1 files changed, 0 insertions, 215 deletions
diff --git a/core/https/client.go b/core/https/client.go
deleted file mode 100644
index e9e8cd82c..000000000
--- a/core/https/client.go
+++ /dev/null
@@ -1,215 +0,0 @@
-package https
-
-import (
- "encoding/json"
- "errors"
- "fmt"
- "io/ioutil"
- "net"
- "sync"
- "time"
-
- "github.com/miekg/coredns/server"
- "github.com/xenolf/lego/acme"
-)
-
-// acmeMu ensures that only one ACME challenge occurs at a time.
-var acmeMu sync.Mutex
-
-// ACMEClient is an acme.Client with custom state attached.
-type ACMEClient struct {
- *acme.Client
- AllowPrompts bool // if false, we assume AlternatePort must be used
-}
-
-// NewACMEClient creates a new ACMEClient given an email and whether
-// prompting the user is allowed. Clients should not be kept and
-// re-used over long periods of time, but immediate re-use is more
-// efficient than re-creating on every iteration.
-var NewACMEClient = func(email string, allowPrompts bool) (*ACMEClient, error) {
- // Look up or create the LE user account
- leUser, err := getUser(email)
- if err != nil {
- return nil, err
- }
-
- // The client facilitates our communication with the CA server.
- client, err := acme.NewClient(CAUrl, &leUser, KeyType)
- if err != nil {
- return nil, err
- }
-
- // If not registered, the user must register an account with the CA
- // and agree to terms
- if leUser.Registration == nil {
- reg, err := client.Register()
- if err != nil {
- return nil, errors.New("registration error: " + err.Error())
- }
- leUser.Registration = reg
-
- if allowPrompts { // can't prompt a user who isn't there
- if !Agreed && reg.TosURL == "" {
- Agreed = promptUserAgreement(saURL, false) // TODO - latest URL
- }
- if !Agreed && reg.TosURL == "" {
- return nil, errors.New("user must agree to terms")
- }
- }
-
- err = client.AgreeToTOS()
- if err != nil {
- saveUser(leUser) // Might as well try, right?
- return nil, errors.New("error agreeing to terms: " + err.Error())
- }
-
- // save user to the file system
- err = saveUser(leUser)
- if err != nil {
- return nil, errors.New("could not save user: " + err.Error())
- }
- }
-
- return &ACMEClient{
- Client: client,
- AllowPrompts: allowPrompts,
- }, nil
-}
-
-// NewACMEClientGetEmail creates a new ACMEClient and gets an email
-// address at the same time (a server config is required, since it
-// may contain an email address in it).
-func NewACMEClientGetEmail(config server.Config, allowPrompts bool) (*ACMEClient, error) {
- return NewACMEClient(getEmail(config, allowPrompts), allowPrompts)
-}
-
-// Configure configures c according to bindHost, which is the host (not
-// whole address) to bind the listener to in solving the http and tls-sni
-// challenges.
-func (c *ACMEClient) Configure(bindHost string) {
- // If we allow prompts, operator must be present. In our case,
- // that is synonymous with saying the server is not already
- // started. So if the user is still there, we don't use
- // AlternatePort because we don't need to proxy the challenges.
- // Conversely, if the operator is not there, the server has
- // already started and we need to proxy the challenge.
- if c.AllowPrompts {
- // Operator is present; server is not already listening
- c.SetHTTPAddress(net.JoinHostPort(bindHost, ""))
- c.SetTLSAddress(net.JoinHostPort(bindHost, ""))
- //c.ExcludeChallenges([]acme.Challenge{acme.DNS01})
- } else {
- // Operator is not present; server is started, so proxy challenges
- c.SetHTTPAddress(net.JoinHostPort(bindHost, AlternatePort))
- c.SetTLSAddress(net.JoinHostPort(bindHost, AlternatePort))
- //c.ExcludeChallenges([]acme.Challenge{acme.TLSSNI01, acme.DNS01})
- }
- c.ExcludeChallenges([]acme.Challenge{acme.TLSSNI01, acme.DNS01}) // TODO: can we proxy TLS challenges? and we should support DNS...
-}
-
-// Obtain obtains a single certificate for names. It stores the certificate
-// on the disk if successful.
-func (c *ACMEClient) Obtain(names []string) error {
-Attempts:
- for attempts := 0; attempts < 2; attempts++ {
- acmeMu.Lock()
- certificate, failures := c.ObtainCertificate(names, true, nil)
- acmeMu.Unlock()
- if len(failures) > 0 {
- // Error - try to fix it or report it to the user and abort
- var errMsg string // we'll combine all the failures into a single error message
- var promptedForAgreement bool // only prompt user for agreement at most once
-
- for errDomain, obtainErr := range failures {
- // TODO: Double-check, will obtainErr ever be nil?
- if tosErr, ok := obtainErr.(acme.TOSError); ok {
- // Terms of Service agreement error; we can probably deal with this
- if !Agreed && !promptedForAgreement && c.AllowPrompts {
- Agreed = promptUserAgreement(tosErr.Detail, true) // TODO: Use latest URL
- promptedForAgreement = true
- }
- if Agreed || !c.AllowPrompts {
- err := c.AgreeToTOS()
- if err != nil {
- return errors.New("error agreeing to updated terms: " + err.Error())
- }
- continue Attempts
- }
- }
-
- // If user did not agree or it was any other kind of error, just append to the list of errors
- errMsg += "[" + errDomain + "] failed to get certificate: " + obtainErr.Error() + "\n"
- }
- return errors.New(errMsg)
- }
-
- // Success - immediately save the certificate resource
- err := saveCertResource(certificate)
- if err != nil {
- return fmt.Errorf("error saving assets for %v: %v", names, err)
- }
-
- break
- }
-
- return nil
-}
-
-// Renew renews the managed certificate for name. Right now our storage
-// mechanism only supports one name per certificate, so this function only
-// accepts one domain as input. It can be easily modified to support SAN
-// certificates if, one day, they become desperately needed enough that our
-// storage mechanism is upgraded to be more complex to support SAN certs.
-//
-// Anyway, this function is safe for concurrent use.
-func (c *ACMEClient) Renew(name string) error {
- // Prepare for renewal (load PEM cert, key, and meta)
- certBytes, err := ioutil.ReadFile(storage.SiteCertFile(name))
- if err != nil {
- return err
- }
- keyBytes, err := ioutil.ReadFile(storage.SiteKeyFile(name))
- if err != nil {
- return err
- }
- metaBytes, err := ioutil.ReadFile(storage.SiteMetaFile(name))
- if err != nil {
- return err
- }
- var certMeta acme.CertificateResource
- err = json.Unmarshal(metaBytes, &certMeta)
- certMeta.Certificate = certBytes
- certMeta.PrivateKey = keyBytes
-
- // Perform renewal and retry if necessary, but not too many times.
- var newCertMeta acme.CertificateResource
- var success bool
- for attempts := 0; attempts < 2; attempts++ {
- acmeMu.Lock()
- newCertMeta, err = c.RenewCertificate(certMeta, true)
- acmeMu.Unlock()
- if err == nil {
- success = true
- break
- }
-
- // If the legal terms changed and need to be agreed to again,
- // we can handle that.
- if _, ok := err.(acme.TOSError); ok {
- err := c.AgreeToTOS()
- if err != nil {
- return err
- }
- continue
- }
-
- // For any other kind of error, wait 10s and try again.
- time.Sleep(10 * time.Second)
- }
-
- if !success {
- return errors.New("too many renewal attempts; last error: " + err.Error())
- }
-
- return saveCertResource(newCertMeta)
-}