Code Monkey home page Code Monkey logo

certmagic's Introduction

CertMagic

Easy and Powerful TLS Automation

The same library used by the Caddy Web Server

Caddy's automagic TLS features—now for your own Go programs—in one powerful and easy-to-use library!

CertMagic is the most mature, robust, and powerful ACME client integration for Go... and perhaps ever.

With CertMagic, you can add one line to your Go application to serve securely over TLS, without ever having to touch certificates.

Instead of:

// plaintext HTTP, gross 🤢
http.ListenAndServe(":80", mux)

Use CertMagic:

// encrypted HTTPS with HTTP->HTTPS redirects - yay! 🔒😍
certmagic.HTTPS([]string{"example.com"}, mux)

That line of code will serve your HTTP router mux over HTTPS, complete with HTTP->HTTPS redirects. It obtains and renews the TLS certificates. It staples OCSP responses for greater privacy and security. As long as your domain name points to your server, CertMagic will keep its connections secure.

Compared to other ACME client libraries for Go, only CertMagic supports the full suite of ACME features, and no other library matches CertMagic's maturity and reliability.

CertMagic - Automatic HTTPS using Let's Encrypt

Menu

Features

  • Fully automated certificate management including issuance and renewal
  • One-line, fully managed HTTPS servers
  • Full control over almost every aspect of the system
  • HTTP->HTTPS redirects
  • Multiple issuers supported: get certificates from multiple sources/CAs for redundancy and resiliency
  • Solves all 3 common ACME challenges: HTTP, TLS-ALPN, and DNS (and capable of others)
  • Most robust error handling of any ACME client
    • Challenges are randomized to avoid accidental dependence
    • Challenges are rotated to overcome certain network blockages
    • 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
  • Written in Go, a language with memory-safety guarantees
  • Powered by ACMEz, the premier ACME client library for Go
  • All libdns DNS providers work out-of-the-box
  • Pluggable storage backends (default: file system)
  • Pluggable key sources
  • Wildcard certificates
  • Automatic OCSP stapling (done right) keeps your sites online!
  • Distributed solving of all challenges (works behind load balancers)
    • Highly efficient, coordinated management in a fleet
    • Active locking
    • Smart queueing
  • Supports "on-demand" issuance of certificates (during TLS handshakes!)
    • Caddy / CertMagic pioneered this technology
    • Custom decision functions to regulate and throttle on-demand behavior
  • Optional event hooks for observation
  • One-time private keys by default (new key for each cert) to discourage pinning and reduce scope of key compromise
  • Works with any certificate authority (CA) compliant with the ACME specification RFC 8555
  • Certificate revocation (please, only if private key is compromised)
  • Must-Staple (optional; not default)
  • Cross-platform support! Mac, Windows, Linux, BSD, Android...
  • Scales to hundreds of thousands of names/certificates per instance
  • Use in conjunction with your own certificates

Requirements

  1. ACME server (can be a publicly-trusted CA, or your own)
  2. Public DNS name(s) you control
  3. Server reachable from public Internet
    • Or use the DNS challenge to waive this requirement
  4. Control over port 80 (HTTP) and/or 443 (HTTPS)
    • Or they can be forwarded to other ports you control
    • Or use the DNS challenge to waive this requirement
    • (This is a requirement of the ACME protocol, not a library limitation)
  5. Persistent storage
    • Typically the local file system (default)
    • Other integrations available/possible
  6. Go 1.21 or newer

Before using this library, your domain names MUST be pointed (A/AAAA records) at your server (unless you use the DNS challenge)!

Installation

$ go get github.com/caddyserver/certmagic

Usage

Package Overview

Certificate authority

This library uses Let's Encrypt by default, but you can use any certificate authority that conforms to the ACME specification. Known/common CAs are provided as consts in the package, for example LetsEncryptStagingCA and LetsEncryptProductionCA.

The Config type

The certmagic.Config struct is how you can wield the power of this fully armed and operational battle station. However, an empty/uninitialized Config is not a valid one! In time, you will learn to use the force of certmagic.NewDefault() as I have.

Defaults

The default Config value is called certmagic.Default. Change its fields to suit your needs, then call certmagic.NewDefault() when you need a valid Config value. In other words, certmagic.Default is a template and is not valid for use directly.

You can set the default values easily, for example: certmagic.Default.Issuer = ....

Similarly, to configure ACME-specific defaults, use certmagic.DefaultACME.

The high-level functions in this package (HTTPS(), Listen(), ManageSync(), and ManageAsync()) use the default config exclusively. This is how most of you will interact with the package. This is suitable when all your certificates are managed the same way. However, if you need to manage certificates differently depending on their name, you will need to make your own cache and configs (keep reading).

Providing an email address

Although not strictly required, this is highly recommended best practice. It allows you to receive expiration emails if your certificates are expiring for some reason, and also allows the CA's engineers to potentially get in touch with you if something is wrong. I recommend setting certmagic.DefaultACME.Email or always setting the Email field of a new Config struct.

Rate limiting

To avoid firehosing the CA's servers, CertMagic has built-in rate limiting. Currently, its default limit is up to 10 transactions (obtain or renew) every 1 minute (sliding window). This can be changed by setting the RateLimitEvents and RateLimitEventsWindow variables, if desired.

The CA may still enforce their own rate limits, and there's nothing (well, nothing ethical) CertMagic can do to bypass them for you.

Additionally, CertMagic will retry failed validations with exponential backoff for up to 30 days, with a reasonable maximum interval between attempts (an "attempt" means trying each enabled challenge type once).

Development and Testing

Note that Let's Encrypt imposes strict rate limits at its production endpoint, so using it while developing your application may lock you out for a few days if you aren't careful!

While developing your application and testing it, use their staging endpoint which has much higher rate limits. Even then, don't hammer it: but it's much safer for when you're testing. When deploying, though, use their production CA because their staging CA doesn't issue trusted certificates.

To use staging, set certmagic.DefaultACME.CA = certmagic.LetsEncryptStagingCA or set CA of every ACMEIssuer struct.

Examples

There are many ways to use this library. We'll start with the highest-level (simplest) and work down (more control).

All these high-level examples use certmagic.Default and certmagic.DefaultACME for the config and the default cache and storage for serving up certificates.

First, we'll follow best practices and do the following:

// read and agree to your CA's legal documents
certmagic.DefaultACME.Agreed = true

// provide an email address
certmagic.DefaultACME.Email = "[email protected]"

// use the staging endpoint while we're developing
certmagic.DefaultACME.CA = certmagic.LetsEncryptStagingCA

For fully-functional program examples, check out this Twitter thread (or read it unrolled into a single post). (Note that the package API has changed slightly since these posts.)

Serving HTTP handlers with HTTPS

err := certmagic.HTTPS([]string{"example.com", "www.example.com"}, mux)
if err != nil {
	return err
}

This starts HTTP and HTTPS listeners and redirects HTTP to HTTPS!

Starting a TLS listener

ln, err := certmagic.Listen([]string{"example.com"})
if err != nil {
	return err
}

Getting a tls.Config

tlsConfig, err := certmagic.TLS([]string{"example.com"})
if err != nil {
	return err
}
// be sure to customize NextProtos if serving a specific
// application protocol after the TLS handshake, for example:
tlsConfig.NextProtos = append([]string{"h2", "http/1.1"}, tlsConfig.NextProtos...)

Advanced use

For more control (particularly, if you need a different way of managing each certificate), you'll make and use a Cache and a Config like so:

// First make a pointer to a Cache as we need to reference the same Cache in
// GetConfigForCert below.
var cache *certmagic.Cache
cache = certmagic.NewCache(certmagic.CacheOptions{
	GetConfigForCert: func(cert certmagic.Certificate) (*certmagic.Config, error) {
		// Here we use New to get a valid Config associated with the same cache.
		// The provided Config is used as a template and will be completed with
		// any defaults that are set in the Default config.
		return certmagic.New(cache, certmagic.Config{
			// ...
		}), nil
	},
	...
})

magic := certmagic.New(cache, certmagic.Config{
	// any customizations you need go here
})

myACME := certmagic.NewACMEIssuer(magic, certmagic.ACMEIssuer{
	CA:     certmagic.LetsEncryptStagingCA,
	Email:  "[email protected]",
	Agreed: true,
	// plus any other customizations you need
})

magic.Issuers = []certmagic.Issuer{myACME}

// this obtains certificates or renews them if necessary
err := magic.ManageSync(context.TODO(), []string{"example.com", "sub.example.com"})
if err != nil {
	return err
}

// to use its certificates and solve the TLS-ALPN challenge,
// you can get a TLS config to use in a TLS listener!
tlsConfig := magic.TLSConfig()

// be sure to customize NextProtos if serving a specific
// application protocol after the TLS handshake, for example:
tlsConfig.NextProtos = append([]string{"h2", "http/1.1"}, tlsConfig.NextProtos...)

//// OR ////

// if you already have a TLS config you don't want to replace,
// we can simply set its GetCertificate field and append the
// TLS-ALPN challenge protocol to the NextProtos
myTLSConfig.GetCertificate = magic.GetCertificate
myTLSConfig.NextProtos = append(myTLSConfig.NextProtos, acmez.ACMETLS1Protocol)

// the HTTP challenge has to be handled by your HTTP server;
// if you don't have one, you should have disabled it earlier
// when you made the certmagic.Config
httpMux = myACME.HTTPChallengeHandler(httpMux)

Great! This example grants you much more flexibility for advanced programs. However, the vast majority of you will only use the high-level functions described earlier, especially since you can still customize them by setting the package-level Default config.

Wildcard certificates

At time of writing (December 2018), Let's Encrypt only issues wildcard certificates with the DNS challenge. You can easily enable the DNS challenge with CertMagic for numerous providers (see the relevant section in the docs).

Behind a load balancer (or in a cluster)

CertMagic runs effectively behind load balancers and/or in cluster/fleet environments. In other words, you can have 10 or 1,000 servers all serving the same domain names, all sharing certificates and OCSP staples.

To do so, simply ensure that each instance is using the same Storage. That is the sole criteria for determining whether an instance is part of a cluster.

The default Storage is implemented using the file system, so mounting the same shared folder is sufficient (see Storage for more on that)! If you need an alternate Storage implementation, feel free to use one, provided that all the instances use the same one. :)

See Storage and the associated pkg.go.dev for more information!

The ACME Challenges

This section describes how to solve the ACME challenges. Challenges are how you demonstrate to the certificate authority some control over your domain name, thus authorizing them to grant you a certificate for that name. The great innovation of ACME is that verification by CAs can now be automated, rather than having to click links in emails (who ever thought that was a good idea??).

If you're using the high-level convenience functions like HTTPS(), Listen(), or TLS(), the HTTP and/or TLS-ALPN challenges are solved for you because they also start listeners. However, if you're making a Config and you start your own server manually, you'll need to be sure the ACME challenges can be solved so certificates can be renewed.

The HTTP and TLS-ALPN challenges are the defaults because they don't require configuration from you, but they require that your server is accessible from external IPs on low ports. If that is not possible in your situation, you can enable the DNS challenge, which will disable the HTTP and TLS-ALPN challenges and use the DNS challenge exclusively.

Technically, only one challenge needs to be enabled for things to work, but using multiple is good for reliability in case a challenge is discontinued by the CA. This happened to the TLS-SNI challenge in early 2018—many popular ACME clients such as Traefik and Autocert broke, resulting in downtime for some sites, until new releases were made and patches deployed, because they used only one challenge; Caddy, however—this library's forerunner—was unaffected because it also used the HTTP challenge. If multiple challenges are enabled, they are chosen randomly to help prevent false reliance on a single challenge type. And if one fails, any remaining enabled challenges are tried before giving up.

HTTP Challenge

Per the ACME spec, the HTTP challenge requires port 80, or at least packet forwarding from port 80. It works by serving a specific HTTP response that only the genuine server would have to a normal HTTP request at a special endpoint.

If you are running an HTTP server, solving this challenge is very easy: just wrap your handler in HTTPChallengeHandler or call SolveHTTPChallenge() inside your own ServeHTTP() method.

For example, if you're using the standard library:

mux := http.NewServeMux()
mux.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {
	fmt.Fprintf(w, "Lookit my cool website over HTTPS!")
})

http.ListenAndServe(":80", myACME.HTTPChallengeHandler(mux))

If wrapping your handler is not a good solution, try this inside your ServeHTTP() instead:

magic := certmagic.NewDefault()
myACME := certmagic.NewACMEIssuer(magic, certmagic.DefaultACME)

func ServeHTTP(w http.ResponseWriter, req *http.Request) {
	if myACME.HandleHTTPChallenge(w, r) {
		return // challenge handled; nothing else to do
	}
	...
}

If you are not running an HTTP server, you should disable the HTTP challenge or run an HTTP server whose sole job it is to solve the HTTP challenge.

TLS-ALPN Challenge

Per the ACME spec, the TLS-ALPN challenge requires port 443, or at least packet forwarding from port 443. It works by providing a special certificate using a standard TLS extension, Application Layer Protocol Negotiation (ALPN), having a special value. This is the most convenient challenge type because it usually requires no extra configuration and uses the standard TLS port which is where the certificates are used, also.

This challenge is easy to solve: just use the provided tls.Config when you make your TLS listener:

// use this to configure a TLS listener
tlsConfig := magic.TLSConfig()

Or make two simple changes to an existing tls.Config:

myTLSConfig.GetCertificate = magic.GetCertificate
myTLSConfig.NextProtos = append(myTLSConfig.NextProtos, acmez.ACMETLS1Protocol}

Then just make sure your TLS listener is listening on port 443:

ln, err := tls.Listen("tcp", ":443", myTLSConfig)

DNS Challenge

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 DNS providers with libdns implementations! It always cleans up the temporary record after the challenge completes.

To enable it, just set the DNS01Solver field on a certmagic.ACMEIssuer struct, or set the default certmagic.ACMEIssuer.DNS01Solver variable. For example, if my domains' DNS was served by Cloudflare:

import "github.com/libdns/cloudflare"

certmagic.DefaultACME.DNS01Solver = &certmagic.DNS01Solver{
	DNSManager: certmagic.DNSManager{
		DNSProvider: &cloudflare.Provider{
			APIToken: "topsecret",
		},
	},
}

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.ACMEIssuer instance.

On-Demand TLS

Normally, certificates are obtained and renewed before a listener starts serving, and then those certificates are maintained throughout the lifetime of the program. In other words, the certificate names are static. But sometimes you don't know all the names ahead of time, or you don't want to manage all the certificates up front. This is where On-Demand TLS shines.

Originally invented for use in Caddy (which was the first program to use such technology), On-Demand TLS makes it possible and easy to serve certificates for arbitrary or specific names during the lifetime of the server. When a TLS handshake is received, CertMagic will read the Server Name Indication (SNI) value and either load and present that certificate in the ServerHello, or if one does not exist, it will obtain it from a CA right then-and-there.

Of course, this has some obvious security implications. You don't want to DoS a CA or allow arbitrary clients to fill your storage with spammy TLS handshakes. That's why, when you enable On-Demand issuance, you should set limits or policy to allow getting certificates. CertMagic has an implicit whitelist built-in which is sufficient for nearly everyone, but also has a more advanced way to control on-demand issuance.

The simplest way to enable on-demand issuance is to set the OnDemand field of a Config (or the default package-level value):

certmagic.Default.OnDemand = new(certmagic.OnDemandConfig)

By setting this to a non-nil value, on-demand TLS is enabled for that config. For convenient security, CertMagic's high-level abstraction functions such as HTTPS(), TLS(), ManageSync(), ManageAsync(), and Listen() (which all accept a list of domain names) will whitelist those names automatically so only certificates for those names can be obtained when using the Default config. Usually this is sufficient for most users.

However, if you require advanced control over which domains can be issued certificates on-demand (for example, if you do not know which domain names you are managing, or just need to defer their operations until later), you should implement your own DecisionFunc:

// if the decision function returns an error, a certificate
// may not be obtained for that name at that time
certmagic.Default.OnDemand = &certmagic.OnDemandConfig{
	DecisionFunc: func(name string) error {
		if name != "example.com" {
			return fmt.Errorf("not allowed")
		}
		return nil
	},
}

The pkg.go.dev describes how to use this in full detail, so please check it out!

Storage

CertMagic relies on storage to store certificates and other TLS assets (OCSP staple cache, coordinating locks, etc). Persistent storage is a requirement when using CertMagic: ephemeral storage will likely lead to rate limiting on the CA-side as CertMagic will always have to get new certificates.

By default, CertMagic stores assets on the local file system in $HOME/.local/share/certmagic (and honors $XDG_DATA_HOME if set). CertMagic will create the directory if it does not exist. If writes are denied, things will not be happy, so make sure CertMagic can write to it!

The notion of a "cluster" or "fleet" of instances that may be serving the same site and sharing certificates, etc, is tied to storage. Simply, any instances that use the same storage facilities are considered part of the cluster. So if you deploy 100 instances of CertMagic behind a load balancer, they are all part of the same cluster if they share the same storage configuration. Sharing storage could be mounting a shared folder, or implementing some other distributed storage system such as a database server or KV store.

The easiest way to change the storage being used is to set certmagic.Default.Storage to a value that satisfies the Storage interface. Keep in mind that a valid Storage must be able to implement some operations atomically in order to provide locking and synchronization.

If you write a Storage implementation, please add it to the project wiki so people can find it!

Cache

All of the certificates in use are de-duplicated and cached in memory for optimal performance at handshake-time. This cache must be backed by persistent storage as described above.

Most applications will not need to interact with certificate caches directly. Usually, the closest you will come is to set the package-wide certmagic.Default.Storage variable (before attempting to create any Configs) which defines how the cache is persisted. However, if your use case requires using different storage facilities for different Configs (that's highly unlikely and NOT recommended! Even Caddy doesn't get that crazy), you will need to call certmagic.NewCache() and pass in the storage you want to use, then get new Config structs with certmagic.NewWithCache() and pass in the cache.

Again, if you're needing to do this, you've probably over-complicated your application design.

Events

(Events are new and still experimental, so they may change.)

CertMagic emits events when possible things of interest happen. Set the OnEvent field of your Config to subscribe to events; ignore the ones you aren't interested in. Here are the events currently emitted along with their metadata you can use:

  • cached_unmanaged_cert An unmanaged certificate was cached
    • sans: The subject names on the certificate
  • cert_obtaining A certificate is about to be obtained
    • renewal: Whether this is a renewal
    • identifier: The name on the certificate
    • forced: Whether renewal is being forced (if renewal)
    • remaining: Time left on the certificate (if renewal)
    • issuer: The previous or current issuer
  • cert_obtained A certificate was successfully obtained
    • renewal: Whether this is a renewal
    • identifier: The name on the certificate
    • remaining: Time left on the certificate (if renewal)
    • issuer: The previous or current issuer
    • storage_path: The path to the folder containing the cert resources within storage
    • private_key_path: The path to the private key file in storage
    • certificate_path: The path to the public key file in storage
    • metadata_path: The path to the metadata file in storage
  • cert_failed An attempt to obtain a certificate failed
    • renewal: Whether this is a renewal
    • identifier: The name on the certificate
    • remaining: Time left on the certificate (if renewal)
    • issuers: The issuer(s) tried
    • error: The (final) error message
  • tls_get_certificate The GetCertificate phase of a TLS handshake is under way
    • client_hello: The tls.ClientHelloInfo struct
  • cert_ocsp_revoked A certificate's OCSP indicates it has been revoked
    • subjects: The subject names on the certificate
    • certificate: The Certificate struct
    • reason: The OCSP revocation reason
    • revoked_at: When the certificate was revoked

OnEvent can return an error. Some events may be aborted by returning an error. For example, returning an error from cert_obtained can cancel obtaining the certificate. Only return an error from OnEvent if you want to abort program flow.

ZeroSSL

ZeroSSL has both ACME and HTTP API services for getting certificates. CertMagic works with both of them.

To use ZeroSSL's ACME server, configure CertMagic with an ACMEIssuer like you would with any other ACME CA (just adjust the directory URL). External Account Binding (EAB) is required for ZeroSSL. You can use the ZeroSSL API to generate one, or your account dashboard.

To use ZeroSSL's API instead, use the ZeroSSLIssuer. Here is a simple example:

magic := certmagic.NewDefault()

magic.Issuers = []certmagic.Issuer{
	certmagic.ZeroSSLIssuer{
		APIKey: "<your ZeroSSL API key>",
	}),
}

err := magic.ManageSync(ctx, []string{"example.com"})

FAQ

Can I use some of my own certificates while using CertMagic?

Yes, just call the relevant method on the Config to add your own certificate to the cache:

Keep in mind that unmanaged certificates are (obviously) not renewed for you, so you'll have to replace them when you do. However, OCSP stapling is performed even for unmanaged certificates that qualify.

Does CertMagic obtain SAN certificates?

Technically all certificates these days are SAN certificates because CommonName is deprecated. But if you're asking whether CertMagic issues and manages certificates with multiple SANs, the answer is no. But it does support serving them, if you provide your own.

How can I listen on ports 80 and 443? Do I have to run as root?

On Linux, you can use setcap to grant your binary the permission to bind low ports:

$ sudo setcap cap_net_bind_service=+ep /path/to/your/binary

and then you will not need to run with root privileges.

Contributing

We welcome your contributions! Please see our contributing guidelines for instructions.

Project History

CertMagic is the core of Caddy's advanced TLS automation code, extracted into a library. The underlying ACME client implementation is 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.

Now, CertMagic is the actual library used by Caddy. It's incredibly powerful and feature-rich, but also easy to use for simple Go programs: one line of code can enable fully-automated HTTPS applications with HTTP->HTTPS redirects.

Caddy is known for its robust HTTPS+ACME features. When ACME certificate authorities have had outages, in some cases Caddy was the only major client that didn't experience any downtime. Caddy can weather OCSP outages lasting days, or CA outages lasting weeks, without taking your sites offline.

Caddy was also the first to sport "on-demand" issuance technology, which obtains certificates during the first TLS handshake for an allowed SNI name.

Consequently, CertMagic brings all these (and more) features and capabilities right into your own Go programs.

You can watch a 2016 dotGo talk by the author of this library about using ACME to automate certificate management in Go programs:

Matthew Holt speaking at dotGo 2016 about ACME in Go

Credits and License

CertMagic is a project by Matthew Holt, who is the author; and various contributors, who are credited in the commit history of either CertMagic or Caddy.

CertMagic is licensed under Apache 2.0, an open source license. For convenience, its main points are summarized as follows (but this is no replacement for the actual license text):

  • The author owns the copyright to this code
  • Use, distribute, and modify the software freely
  • Private and internal use is allowed
  • License text and copyright notices must stay intact and be included with distributions
  • Any and all changes to the code must be documented

certmagic's People

Contributors

256dpi avatar ankon avatar crccw avatar crvv avatar delthas avatar dependabot[bot] avatar directionless avatar emersion avatar francislavoie avatar gjung56 avatar goksan avatar jimen0 avatar kenjenkins avatar kizmc avatar mholt avatar mohammed90 avatar oliverpool avatar pgeh avatar pwilloughby avatar renthraysk avatar ryankoski avatar s111 avatar sam-lord avatar securityclippy avatar shitz avatar simaotwx avatar skeetmtp avatar skirsten avatar wusatosi avatar x1nchen avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

certmagic's Issues

Certmagic crash when using Caddy with Gandi DNS plugin

What version of the package are you using?

v0.11.3-0.20200810220624-10a8b5c72339

What are you trying to do?

Run a custom build of Caddy in docker, including gandi-dns.

What steps did you take?

mafrosis@MBP: ~/test > docker-compose up caddy
Recreating caddy ... done
Attaching to caddy
caddy_1           | {"level":"info","ts":1597182750.309071,"msg":"using provided configuration","config_file":"/etc/caddy/Caddyfile","config_adapter":"caddyfile"}
caddy_1           | {"level":"warn","ts":1597182750.313778,"logger":"admin","msg":"admin endpoint disabled"}
caddy_1           | {"level":"info","ts":1597182750.3142364,"logger":"tls.cache.maintenance","msg":"started background certificate maintenance","cache":"0xc00067afc0"}
caddy_1           | {"level":"info","ts":1597182750.3197625,"logger":"http","msg":"enabling automatic HTTP->HTTPS redirects","server_name":"srv0"}
caddy_1           | {"level":"info","ts":1597182750.3244257,"logger":"http","msg":"enabling strict SNI-Host matching because TLS client auth is configured","server_name":"srv0"}
caddy_1           | {"level":"debug","ts":1597182750.3251233,"logger":"http","msg":"starting server loop","address":"[::]:2020","http3":false,"tls":true}
caddy_1           | {"level":"debug","ts":1597182750.3252916,"logger":"http","msg":"starting server loop","address":"[::]:80","http3":false,"tls":false}
caddy_1           | {"level":"info","ts":1597182750.3253632,"logger":"http","msg":"enabling automatic TLS certificate management","domains":["ha.mafro.net"]}
caddy_1           | {"level":"info","ts":1597182750.3260002,"msg":"autosaved config","file":"/config/caddy/autosave.json"}
caddy_1           | {"level":"info","ts":1597182750.326079,"msg":"serving initial configuration"}
caddy_1           | {"level":"info","ts":1597182750.3263528,"logger":"tls","msg":"cleaned up storage units"}
caddy_1           | {"level":"info","ts":1597182750.331991,"logger":"tls.obtain","msg":"acquiring lock","identifier":"ha.mafro.net"}
caddy_1           | {"level":"info","ts":1597182750.3322325,"logger":"tls.obtain","msg":"lock acquired","identifier":"ha.mafro.net"}
caddy_1           | {"level":"info","ts":1597182750.3455365,"logger":"tls.issuance.acme","msg":"waiting on internal rate limiter","identifiers":["ha.mafro.net"]}
caddy_1           | {"level":"info","ts":1597182750.3459635,"logger":"tls.issuance.acme","msg":"done waiting on internal rate limiter","identifiers":["ha.mafro.net"]}
caddy_1           | {"level":"debug","ts":1597182751.2572203,"logger":"tls.issuance.acme.acme_client","msg":"http request","method":"GET","url":"https://ca.mafro.net/acme/acme/directory","headers":{"User-Agent":["Caddy/2.2.0-rc.1 CertMagic acmez (linux; amd64)"]},"status_code":200,"response_headers":{"Cache-Control":["no-store"],"Content-Length":["282"],"Content-Type":["application/json"],"Date":["Tue, 11 Aug 2020 21:52:48 GMT"],"Replay-Nonce":["WDNMcWxCQjBvUW5XQWxNbWtSQjZzWXFUTmUwVzBRUEw"]}}
caddy_1           | {"level":"info","ts":1597182751.2575085,"logger":"tls.obtain","msg":"releasing lock","identifier":"ha.mafro.net"}
caddy_1           | 2020/08/11 21:52:31 panic: certificate worker: runtime error: invalid memory address or nil pointer dereference
caddy_1           | goroutine 57 [running]:
caddy_1           | github.com/caddyserver/certmagic.(*jobManager).worker.func1()
caddy_1           | 	github.com/caddyserver/[email protected]/async.go:58 +0x9e
caddy_1           | panic(0x14f1320, 0x2596560)
caddy_1           | 	runtime/panic.go:969 +0x166
caddy_1           | github.com/mholt/acmez/acme.(*stack).push(0x0, 0xc000042c60, 0x2b)
caddy_1           | 	github.com/mholt/[email protected]/acme/client.go:199 +0x4b
caddy_1           | github.com/mholt/acmez/acme.(*Client).doHTTPRequest(0xc0002fa480, 0xc000652100, 0xc0004b3710, 0xc00016c2d0, 0xc0003bc900, 0x0, 0x0)
caddy_1           | 	github.com/mholt/[email protected]/acme/http.go:291 +0x2db
caddy_1           | github.com/mholt/acmez/acme.(*Client).httpReq(0xc0002fa480, 0x1a03640, 0xc0006ea6c0, 0x16bf9cc, 0x3, 0xc0003bc960, 0x28, 0x0, 0x0, 0x0, ...)
caddy_1           | 	github.com/mholt/[email protected]/acme/http.go:176 +0x1e6
caddy_1           | github.com/mholt/acmez/acme.(*Client).provisionDirectory(0xc0002fa480, 0x1a03640, 0xc0006ea6c0, 0x0, 0x0)
caddy_1           | 	github.com/mholt/[email protected]/acme/client.go:125 +0x1bc
caddy_1           | github.com/mholt/acmez/acme.(*Client).provision(0xc0002fa480, 0x1a03640, 0xc0006ea6c0, 0x0, 0x0)
caddy_1           | 	github.com/mholt/[email protected]/acme/client.go:95 +0x92
caddy_1           | github.com/mholt/acmez/acme.(*Client).NewOrder(0xc0002fa480, 0x1a03640, 0xc0006ea6c0, 0xc000368d28, 0x5, 0x0, 0x0, 0x0, 0x0, 0x0, ...)
caddy_1           | 	github.com/mholt/[email protected]/acme/order.go:106 +0xc4
caddy_1           | github.com/mholt/acmez.(*Client).ObtainCertificateUsingCSR(0xc0004a2640, 0x1a03640, 0xc0006ea6c0, 0xc000368d28, 0x5, 0x0, 0x0, 0x0, 0x0, 0x0, ...)
caddy_1           | 	github.com/mholt/[email protected]/client.go:123 +0x3a4
caddy_1           | github.com/caddyserver/certmagic.(*ACMEManager).doIssue(0xc0002fa3c0, 0x1a03640, 0xc0006ea6c0, 0xc00013c900, 0xc0006e8700, 0xc0005a7080, 0x40d1e8, 0xc0, 0x167cfc0)
caddy_1           | 	github.com/caddyserver/[email protected]/acmemanager.go:299 +0x1a1
caddy_1           | github.com/caddyserver/certmagic.(*ACMEManager).Issue(0xc0002fa3c0, 0x1a03640, 0xc0006ea6c0, 0xc00013c900, 0x0, 0x0, 0x0)
caddy_1           | 	github.com/caddyserver/[email protected]/acmemanager.go:228 +0xdb
caddy_1           | github.com/caddyserver/caddy/v2/modules/caddytls.(*ACMEIssuer).Issue(0xc0003e2c80, 0x1a03640, 0xc0006ea6c0, 0xc00013c900, 0x1, 0x1, 0xc00013c900)
caddy_1           | 	github.com/caddyserver/caddy/[email protected]/modules/caddytls/acmeissuer.go:190 +0xda
caddy_1           | github.com/caddyserver/certmagic.(*Config).obtainWithIssuer.func2(0x1a03640, 0xc0006ea6c0, 0x2, 0x1)
caddy_1           | 	github.com/caddyserver/[email protected]/config.go:448 +0x397
caddy_1           | github.com/caddyserver/certmagic.doWithRetry(0x1a03640, 0xc0006ea6c0, 0xc00044af00, 0xc0005a7aa0, 0x1, 0x1)
caddy_1           | 	github.com/caddyserver/[email protected]/async.go:106 +0x251
caddy_1           | github.com/caddyserver/certmagic.(*Config).obtainWithIssuer(0xc00041e6c0, 0x1a03580, 0xc0003704c0, 0x19f0140, 0xc0003e2c80, 0xc0002274d0, 0xc, 0x19f0100, 0x0, 0x0)
caddy_1           | 	github.com/caddyserver/[email protected]/config.go:477 +0x2ef
caddy_1           | github.com/caddyserver/certmagic.(*Config).ObtainCert(0xc00041e6c0, 0x1a03580, 0xc0003704c0, 0xc0002274d0, 0xc, 0x0, 0x0, 0x0)
caddy_1           | 	github.com/caddyserver/[email protected]/config.go:385 +0x146
caddy_1           | github.com/caddyserver/certmagic.(*Config).manageOne.func1(0x0, 0x0)
caddy_1           | 	github.com/caddyserver/[email protected]/config.go:310 +0x92
caddy_1           | github.com/caddyserver/certmagic.(*jobManager).worker(0x259f820)
caddy_1           | 	github.com/caddyserver/[email protected]/async.go:73 +0x102
caddy_1           | created by github.com/caddyserver/certmagic.(*jobManager).Submit
caddy_1           | 	github.com/caddyserver/[email protected]/async.go:50 +0x12d
^CGracefully stopping... (press Ctrl+C again to force)
Stopping caddy   ... done

Caddyfile:

{
	debug
	admin off

	acme_ca      https://ca.mafro.net/acme/acme/directory
	acme_ca_root /root/ca.crt
}

ha.mafro.net:2020

tls {
	dns gandi {env.GANDI_API_TOKEN}

	client_auth {
		mode                 require_and_verify
		trusted_ca_cert_file /root/ca.crt
	}
}

reverse_proxy ringil:8123

docker-compose.yml:

---
version: '3'

volumes:
  caddy-data: {}

services:
  caddy:
    image: mafrosis/caddy-with-gandi-dns:master
    restart: always
    build:
      context: .
      dockerfile: Dockerfile.caddy
    environment:
      - GANDI_API_TOKEN
    ports:
      - 2020:2020
    volumes:
      - caddy-data:/data
      - ./Caddyfile:/etc/caddy/Caddyfile:ro
      - ./certs/root_ca.crt:/root/ca.crt:ro

Dockerfile:

FROM golang:1.14-alpine as builder

ARG CADDY_VERSION=v2.1.1
ARG CADDY_GANDI_PLUGIN=v1.0.0
ARG XCADDY_VERSION=0.1.5
ARG ARCH=amd64

# dont bother cleaning up since we're in docker multistage
ENV XCADDY_SKIP_CLEANUP=1

RUN apk add --no-cache curl git

RUN curl -o /tmp/xcaddy.tgz -L "https://github.com/caddyserver/xcaddy/releases/download/v${XCADDY_VERSION}/xcaddy_${XCADDY_VERSION}_linux_${ARCH}.tar.gz"
RUN tar xzf /tmp/xcaddy.tgz -C /tmp && cp /tmp/xcaddy /usr/local/bin && chmod +x /usr/local/bin/xcaddy

RUN xcaddy build ${CADDY_VERSION} --with "github.com/caddy-dns/gandi@${CADDY_GANDI_PLUGIN}"


FROM alpine:3.12

RUN apk add --no-cache ca-certificates mailcap bind-tools

# https://github.com/caddyserver/dist/commits
ENV CADDY_DIST_COMMIT ce8860dbfe32fab755b5f87e4f06373c0c849d13

RUN set -eux; \
	mkdir -p \
		/config/caddy \
		/data/caddy \
		/etc/caddy \
		/usr/share/caddy \
	; \
	wget -O /etc/caddy/Caddyfile "https://github.com/caddyserver/dist/raw/$CADDY_DIST_COMMIT/config/Caddyfile"; \
	wget -O /usr/share/caddy/index.html "https://github.com/caddyserver/dist/raw/$CADDY_DIST_COMMIT/welcome/index.html"

# https://github.com/caddyserver/caddy/releases
ARG CADDY_VERSION=${CADDY_VERSION}

COPY --from=builder /go/caddy /usr/local/bin/caddy

# set up nsswitch.conf for Go's "netgo" implementation
# - https://github.com/docker-library/golang/blob/1eb096131592bcbc90aa3b97471811c798a93573/1.14/alpine3.12/Dockerfile#L9
RUN [ ! -e /etc/nsswitch.conf ] && echo 'hosts: files dns' > /etc/nsswitch.conf

# See https://caddyserver.com/docs/conventions#file-locations for details
ENV XDG_CONFIG_HOME=/config
ENV XDG_DATA_HOME=/data

VOLUME /config
VOLUME /data

EXPOSE 80
EXPOSE 443
EXPOSE 2019

WORKDIR /srv

CMD ["caddy", "run", "--config", "/etc/caddy/Caddyfile", "--adapter", "caddyfile"]

What did you expect to happen, and what actually happened instead?

Caddy crashes on startup

how do i host 100000 domain names?

how do i host 100000 domain names?

what's the code to "automagically" detect domain name and just use it? right now i see a lot of manual entry to the domain name section

QUIC support?

What is your question?

Now that a whole 3 days have passed since merging QUIC support into Caddy (caddyserver/caddy#2727), any plans to bring QUIC to certmagic? Would that even make sense or do the hooks already exist to use both?

Bonus: What do you use this package for, and does it help you?

Simple and free TLS, it's amazing! Thanks for your generous work.

Non SNI Certificates

Commit c1d472b made certmagic set the listeners IP address as the certificate name in case the server name has not been provided (not using SNI).

In a current case I have an app with certmagic running in docker. If a non SNI client is connecting, certmagic sets the private 172.17.X ip address of the docker container as the certificate name and since there is no certificate managed under that name it logs the error "no certificate available for 172.17.X".

Can we add a switch to the configuration to set a fallback certificate name for such cases? In my case I run certmagic with a single domain and would like to use the same certificate also for non SNI clients.

Maybe something like:

certmagic.NonSNIDomain = "example.com"

In case the option is not set, we can fallback to the current behaviour.

Redirection

What is your question?

I would like requests to http (port 80) to be forwarded to https (port 443).

What have you already tried?

I tried looking for "forward" in your docs, and I'm not finding any examples on how to forward requests from port 80 --> 443

Currently I bring up the server like so

func serveHttps(mux *http.ServeMux, domain string, email string, port string, portSSL string, timeout time.Duration) {
	tls := certmagic.NewDefault()
	tls.Email = email
	tls.CA = certmagic.LetsEncryptProductionCA
	tls.Agreed = true
	if err := tls.ManageSync([]string{domain}); err != nil {
		log.Fatal(fmt.Sprintf("failed to run it %q", err))
	}
	serverSSL := http.Server{
		Addr:         address(portSSL),
		Handler:      mux,
		TLSConfig:    tls.TLSConfig(),
		ReadTimeout:  timeout,
		WriteTimeout: timeout,
	}
	server := http.Server{
		Addr:         address(port),
		Handler:      tls.HTTPChallengeHandler(mux),
		ReadTimeout:  timeout,
		WriteTimeout: timeout,
	}
	log.Println("listening ssl...")
	go server.ListenAndServe()
	go serverSSL.ListenAndServeTLS("", "")
}

Both port 80 and 443 respond, but there is no forwarding of the request from port 80 -> 443

Include any other information or discussion.

I'm new to this library, anything glaringly bad for this set up? It will be exposed to the inter webs -- particularly my use of ManageSync (not sure what benefits ManageAsync would have)

Bonus: What do you use this package for, and does it help you?

Fun things

redirect IP address to domain name

What would you like to have changed?

  • redirect requests to an IP address to the domain name.
    Right now, with a site powered by certmagic, a request like
curl -vvIL http://<My-IP-address>

results in;

* Connected to <My-IP-address> (<My-IP-address>) port 80 (#0)
HEAD / HTTP/1.1
Host: <My-IP-address>
User-Agent: curl/7.58.0
Accept: application/json, */*

HTTP/1.1 301 Moved Permanently
Connection: close
Content-Type: text/html; charset=utf-8
Location: https://<My-IP-address>/
Date: Sat, 30 Nov 2019 20:24:48 GMT

* Closing connection 0
* Issue another request to this URL: 'https://<My-IP-address>/'
*   Trying <My-IP-address>...
* TCP_NODELAY set
* Connected to <My-IP-address> (<My-IP-address>) port 443 (#1)
* ALPN, offering h2
* ALPN, offering http/1.1
* successfully set certificate verify locations:
*   CAfile: /etc/ssl/certs/ca-certificates.crt
*   CApath: /etc/ssl/certs


* TLSv1.3 (OUT), TLS handshake, Client hello (1):
* TLSv1.3 (IN), TLS alert, Server hello (2):
* error:14094438:SSL routines:ssl3_read_bytes:tlsv1 alert internal error
* stopped the pause stream!
* Closing connection 1
curl: (35) error:14094438:SSL routines:ssl3_read_bytes:tlsv1 alert internal error

This proposal is that such a request should succed by been redirected to https://my_domain.com

Why is this feature a useful, necessary, and/or important addition to this project?

  • there maybe clients that perform dns lookup of a domain name and then issue requests to the returned IP address directly
  • it sounds like the right thing to do
  • it is what other webservers do, here is similar request been issued to google;
dig @8.8.8.8 google.com

;; ANSWER SECTION:
google.com.		140	IN	A	216.58.223.78
curl -vvIL http://216.58.223.78

 Connected to 216.58.223.78 (216.58.223.78) port 80 (#0)
HEAD / HTTP/1.1
Host: 216.58.223.78
User-Agent: curl/7.58.0
Accept: application/json, */*

HTTP/1.1 301 Moved Permanently
Location: http://www.google.com/

* Connection #0 to host 216.58.223.78 left intact
* Issue another request to this URL: 'http://www.google.com/'
* Connected to www.google.com (216.58.223.68) port 80 (#1)
HEAD / HTTP/1.1
Host: www.google.com
User-Agent: curl/7.58.0
Accept: application/json, */*

HTTP/1.1 200 OK
Date: Sat, 30 Nov 2019 20:30:02 GMT
Expires: -1
Cache-Control: private, max-age=0

What alternatives are there, or what are you doing in the meantime to work around the lack of this feature?

  • N/A

Please link to any relevant issues, pull requests, or other discussions.

backoff using module

On Go 1.13.5, I believe the Readme will need to update for module support?

 go get github.com/mholt/certmagic
package github.com/cenkalti/backoff/v3: cannot find package "github.com/cenkalti/backoff/v3" in any of:
	/usr/local/go/src/github.com/cenkalti/backoff/v3 (from $GOROOT)
	/Users/s/go/src/github.com/cenkalti/backoff/v3 (from $GOPATH)

Delete expired certificates from storage

What would you like to have changed?

Certificates that are found in storage which have expired should be deleted, just like OCSP staples are.

Why is this feature a useful, necessary, and/or important addition to this project?

Cleans up disk or DB space.

What alternatives are there, or what are you doing in the meantime to work around the lack of this feature?

Delete them manually.

Please link to any relevant issues, pull requests, or other discussions.

StorageAPI agnostic from datastore

What would you like to have changed?

Currently the Storage interface makes many references to store items in a file system. Consider reducing the coupling to the file system.

What Why Workaround
In the Storage and Locker interfaces, the methods take key strings that are in the format of a filesystem path This could make it difficult for different storage implementations to look up certificates. In the past, the Storage interface used the host as the way to reference a certificate/private key, why the switch away from this? parse out the host from the key
In the Storage interface, List takes in a prefix and recursive Consider removing the recursive options since it exists only for keys that are in filesystems hacks to make this work outside of filesystems
In the Storage interface, the Stat method returns a KeyInfo which has Size and IsTerminal these are hard to compute for non-filesystem storage mechanisms these don't appear to be used anywhere, so can always return 0 and false.

Why is this feature a useful, necessary, and/or important addition to this project?

Allows for non-file based storage implementations (eg: MySQL).

Ensure locks are always cleared when shutting down

What version of the package are you using?

f1fdb7f

What are you trying to do?

People are reporting blocked cert operations, presumably because locks remain after forceful shutdown (although, this has not yet been verified because reporters keep deleting the whole acme folder before reporting!).

What steps did you take?

Unsure; others have reported this. See linked reports.

What did you expect to happen, and what actually happened instead?

Locks should be removed when operations are not happening; but apparently they continue to exist. This is likely due to a forceful quit but we aren't sure.

How do you think this should be fixed?

We might already be doing all we can to remove locks properly when shutting down. We just need to verify this. There is nothing we can do if forcefully terminated. Locks already expire after 2 hours.

We need more reports with more information to verify this claim.

Please link to any related issues, pull requests, and/or discussion

builds with Go 1.12 are broken

What would you like to have changed?

The older versions don't work any more due to some dependencies being force pushed. The latest version doesn't work with Go 1.12.x. Projects which depend on certmagic can't be built with Go 1.12.
0.7.2 no longer works because https://github.com/labbsr0x/goh has been force pushed.

Why is this feature a useful, necessary, and/or important addition to this project?

Builds should still work with the old stable Go version.

Compliation error (probably due to lego)

I'm trying to update my version of certmagic from f00a66c to 707b204 but my build is failing.

vendor/github.com/mholt/certmagic/client.go:102:10: legoCfg.Certificate undefined (type *lego.Config has no field or method Certificate)
vendor/github.com/mholt/certmagic/client.go:102:25: undefined: lego.CertificateConfig
vendor/github.com/mholt/certmagic/client.go:200:26: c.acmeClient.Challenge.Remove undefined (type *resolver.SolverManager has no field or method Remove)
vendor/github.com/mholt/certmagic/client.go:203:26: c.acmeClient.Challenge.Remove undefined (type *resolver.SolverManager has no field or method Remove)
vendor/github.com/mholt/certmagic/client.go:207:25: c.acmeClient.Challenge.Remove undefined (type *resolver.SolverManager has no field or method Remove)
vendor/github.com/mholt/certmagic/client.go:208:25: c.acmeClient.Challenge.Remove undefined (type *resolver.SolverManager has no field or method Remove)

Replace the underlying ACME library

I've started work on a fork of go-acme/lego which will eventually be used by CertMagic.

History

Originally -- back in early 2015, well before Let's Encrypt's public beta -- lego was developed by @xenolf, conceived for use in Caddy as a Let's Encrypt client (hence the name lego, "Let's Encrypt for Go"). It was the first ACME client written in Go, and Caddy was the first public/generalized consumer of Let's Encrypt certificates using Go. Eventually, xenolf stepped down to focus on other things, and lead maintainership fell to Ludovic Fernandez, a Traefik engineer at Containous, who has done excellent work keeping lego active and useful, including a complete rewrite to better accommodate ACMEv2, which is actually the first formally standardized version of the ACME spec. Thank you, Ludovic, for all your hard work, especially on many long nights and weekends!

Eventually, Caddy's certificate management logic was surgically extracted into a new library, CertMagic, which is one of the primary consumers of lego (and Caddy is the primary consumer of CertMagic). CertMagic has seen adoption beyond just Caddy, too, used in who knows how many Go projects around the globe.

You could say that Caddy and CertMagic have been through a lot together. Through building, deploying, and working with users on these projects, I've seen all sorts of successes and failures, bugs and edge cases, enterprise requirements and various developer preferences and habits. CertMagic's design has evolved to be more flexible and accommodate as many scenarios as possible, incorporating the things we've learned along the way. And together with multiple organizations, we've written a document of ACME best practices.

Through these experiences, I would be so bold as to suggest that CertMagic is perhaps one of the most well-vetted, production-tested, self-contained certificate management libraries out there (even among those not written in Go). It is designed to scale to tens of thousands of certificates (or orders of magnitudes more, if your machine has enough memory, and if you have enough time to operate within CA rate limits at that scale). It can even obtain certificates dynamically at handshake-time, or coordinate certificate management across a cluster if so configured! I am not aware of other libraries or tools which are designed to scale to these levels or accommodate as many use cases... it is not easy, for sure.

Changes

Now that the ACME spec has mostly settled down, I'd like to say that CertMagic is almost done. But, there's still quite a few things that need improvement, which are difficult without some changes in lego... and while lego is excellent in many ways, but it crumbles at scale. We've worked around a few of the problems in Caddy/CertMagic but it's far from ideal. The changes I want in lego may be considered significant... lego is on major version 3, making a major version 4 is not the problem. It's simply that these changes require a fundamentally different philosophy and vision.

Our opinions or priorities diverge on matters of:

Understandably, most of these differences of opinion are goal-oriented. These are pain points for Caddy/CertMagic, but maybe not necessarily for Traefik or the other consumers of the lego lib: our projects all have different goals, priorities, and customers. Even if we all agree that logging or error handling could be improved, our priorities and visions may differ. I want to be sure we accommodate enterprise scale use cases and failure scenarios that our own customers have experienced, at least. To that end, lego's current design does not accommodate all of Caddy's/CertMagic's requirements, necessitating major changes that would be a burden to its current maintainers, who volunteer their spare time to work on it. I just can't ask more of them after all their hard work.

To avoid added friction to the lego project and to reduce burden on lego maintainers, I will make a derivation of the lego project in a separate repository and have CertMagic use my derivation of lego.

Vision

With the changes I hope to implement more freely in a fork, I expect that:

  • CertMagic will better handle large-scale deployments, especially in error situations.
  • CertMagic's code will slim down in some areas. Several work-arounds exist in CertMagic (such as using all enabled challenge types) that would ideally be fixed in the underlying library instead. These will be fixed in the refactored design of the lego fork.
  • Overall code bloat will decrease as well, since we don't need a command (CertMagic's CLI is Caddy).
  • Build dependencies will be dramatically reduced, with a go.sum file about 6% of the current size. This makes things much easier and more lightweight for development, and significantly lowers error possibilities at build-time.
  • We can reduce load on ACME CAs with internal throttling of ACME transactions, which is not currently exposed for us to control.
  • Performance (speed) of ACME operations will increase by properly managing and reusing state.
  • Configuration will be more flexible all-around without needing to rely on global state.
  • Caddy will be more efficient with frequent, large config reloads on busy servers thanks to proper cancellation of long-running validations.
  • It will be easier and more flexible to configure more DNS providers for the DNS challenge.
  • Errors will be less opaque, so they can be more intelligently handled, and occasions (and durations) of being rate-limited by Let's Encrypt will drop dramatically.

And these are just the foreseeable benefits.

Caddy's certificate management -- via CertMagic -- is already the gold standard. I'm hoping to raise the bar and keep it the best in a world that relies more and more on TLS. Today in 2020, Caddy is still the only web server to use HTTPS automatically and by default. Back in 2015, I thought that would be a short-lived novelty, maybe a cute feature, but 5 years later, it's still unique and magical. In a way, that's kind of disappointing. But we should strive to make privacy the default on the majority of sites, and if that means making Caddy (or CertMagic) the majority web server, so be it.

FAQ

What does this mean for Caddy users?

Except for the potential benefits described above, almost nothing. Caddy doesn't really expose much of the interop with go-acme/lego directly.

What does this mean for CertMagic users?

Except for the potential benefits described above, probably some API changes if you are using CertMagic programmatically. CertMagic hasn't tagged 1.0 yet, so pay attention to breaking changes in the minor releases (v0.X) until then.

What does this mean for lego users?

If you use lego's CLI, you can keep using lego. (Although, we recommend using a long-running server in the long term instead of cron jobs; services like Caddy or CertMagic have more robust error handling!) If you use lego as a library, you can keep using lego. In time, I believe the two code bases will diverge enough that you won't recognize that one is a fork of the other.

If desired, I can contribute back any requested changes if they're compatible with lego, subject to time and funding constraints.

Which lib should I use?

Between lego and my fork? Probably neither, to be honest. Unless you're writing certificate management libraries yourself, most people should use CertMagic instead because one-off certificate operations aren't very useful. Certificates need to be renewed, OCSP needs careful management, etc. For most Go programs in general, CertMagic is a better fit.

If you happen to be writing certificate management software and can't use or contribute to CertMagic for some reason, then that choice is up to you. Lego is probably going to be more stable in the near-future, but I suspect that our derived library will have significant novel improvements as well.

Where will the forked library be hosted?

Its temporarily -- possibly permanent home -- is mholt/acmez, which I will be working on as time and funding permits.

What can the community do to help?

You can sponsor my work, and once I get the library to a point where it can be used, test the heck out of it. :) Also feel free to participate in discussions that arise in the process! Thank you for any help!

Compliation error

This error occured when I try to update certmagic

$ go get -u github.com/mholt/certmagic
# github.com/mholt/certmagic
src/github.com/mholt/certmagic/client.go:98:29: cannot use &leUser (type *user) as type "github.com/go-acme/lego/registration".User in argument to lego.NewConfig:
        *user does not implement "github.com/go-acme/lego/registration".User (wrong type for GetRegistration method)
                have GetRegistration() *"github.com/xenolf/lego/registration".Resource
                want GetRegistration() *"github.com/go-acme/lego/registration".Resource
src/github.com/mholt/certmagic/client.go:103:11: cannot use keyType (type "github.com/xenolf/lego/certcrypto".KeyType) as type "github.com/go-acme/lego/certcrypto".KeyType in field value
src/github.com/mholt/certmagic/client.go:128:72: cannot use "github.com/xenolf/lego/registration".RegisterOptions literal (type "github.com/xenolf/lego/registration".RegisterOptions) as type "github.com/go-acme/lego/registration".RegisterOptions in argument to client.Registration.Register
src/github.com/mholt/certmagic/client.go:132:23: cannot use reg (type *"github.com/go-acme/lego/registration".Resource) as type *"github.com/xenolf/lego/registration".Resource in assignment
src/github.com/mholt/certmagic/client.go:200:33: cannot use "github.com/xenolf/lego/challenge".HTTP01 (type "github.com/xenolf/lego/challenge".Type) as type "github.com/go-acme/lego/challenge".Type in argument to c.acmeClient.Challenge.Remove
src/github.com/mholt/certmagic/client.go:203:33: cannot use "github.com/xenolf/lego/challenge".TLSALPN01 (type "github.com/xenolf/lego/challenge".Type) as type "github.com/go-acme/lego/challenge".Type in argument to c.acmeClient.Challenge.Remove
src/github.com/mholt/certmagic/client.go:207:32: cannot use "github.com/xenolf/lego/challenge".HTTP01 (type "github.com/xenolf/lego/challenge".Type) as type "github.com/go-acme/lego/challenge".Type in argument to c.acmeClient.Challenge.Remove
src/github.com/mholt/certmagic/client.go:208:32: cannot use "github.com/xenolf/lego/challenge".TLSALPN01 (type "github.com/xenolf/lego/challenge".Type) as type "github.com/go-acme/lego/challenge".Type in argument to c.acmeClient.Challenge.Remove
src/github.com/mholt/certmagic/client.go:259:54: cannot use request (type "github.com/xenolf/lego/certificate".ObtainRequest) as type "github.com/go-acme/lego/certificate".ObtainRequest in argument to c.acmeClient.Certificate.Obtain
src/github.com/mholt/certmagic/client.go:271:34: cannot use certificate (type *"github.com/go-acme/lego/certificate".Resource) as type *"github.com/xenolf/lego/certificate".Resource in argument to c.config.saveCertResource
src/github.com/mholt/certmagic/client.go:271:34: too many errors

Simpler Storage API

It's probably too late to change the API now, so this is more of a question about why the API is designed the way it is than anything.

What would you like to have changed?

Remove Locker from the Storage interface.

Why is this feature a useful, necessary, and/or important addition to this project?

TryLock is hard to implement correctly and the implementation doesn't seem to make use of it, because it just waits anyway.

What alternatives are there, or what are you doing in the meantime to work around the lack of this feature?

The ideal interface would be Storage on its own without Locker, then all methods would be required to be safe for concurrent use.
Apart from being more idiomatic, it makes things a lot simpler because the actual storage implementation has a better idea about how to do locking than CertMagic.

The main motivation was to implement a Storage in SQLite (or other databases) but I was blocked by the requirement that TryLock being non-blocking.
Instead of implementing the equivalent of mu.Lock(), it seemed I'd have to introduce a whole bunch of state that will probably be buggy for unknown reasons. Even FileStorage.TryLock makes me nervous because at a quick glance, it doesn't seem to handle cleanup so if the process crashes, everything is probably borked.

Road to 1.0.0

Does a roadmap for v1.0.0 exist or is it rolling development only? It would be nice to have tagged releases of this library for dependency management and stability :-)

How to manage multiple accounts with certmagic/lego

What is your question?

Hello, I need to manage more than 300 domains (which is the rate limit per 3 hours) and may need to have multiple accounts to issue/renew the cert.

My question is about the private key generated for each account, can I use the same private key for multiple accounts or each account must have a separate private key.

Is this private key only used when issuing or renewing a cert?

What have you already tried?

I have tried single account and it works well

Include any other information or discussion.

I don't really fall into large hosting provider limit so LE will most likely reject my request
https://docs.google.com/forms/d/e/1FAIpQLSetFLqcyPrnnrom2Kw802ZjukDVex67dOM2g4O8jEbfWFs3dA/viewform

Bonus: What do you use this package for, and does it help you?

I am really inspired by his package and thx for your wonderful work, I am using lego go wrapper but would likely migrate to certmagic soon

Error on install and compile.

I did a go get of this package, and this error popped up.

# github.com/mholt/certmagic ../../mholt/certmagic/client.go:143:8: ht.ForceAttemptHTTP2 undefined (type *http.Transport has no field or method ForceAttemptHTTP2).

go version:

go version go1.10.4 linux/amd64

Edit:

So after some digging around, in go/src/golang.org/x/net/http2/transport.go, struct Transport does not declare ForceAttemptHTTP2, but looking at https://github.com/golang/go/blob/master/src/net/http/transport.go, struct Transport does declare ForceAttemptHTTP2.

If HTTP/TLS-ALPN challenge fails, try the other one

What would you like to have changed?

Explicitly try the other challenge type if one of HTTP or TLS-ALPN challenges fail.

Why is this feature a useful, necessary, and/or important addition to this project?

Some sites are behind a CDN that terminates TLS, so there is no way the TLS-ALPN challenge will work, and most site owners don't think to disable it in those cases.

What alternatives are there, or what are you doing in the meantime to work around the lack of this feature?

Disable the challenge that won't work. That should still be done probably, to avoid unnecessary work, but at least with this feature implemented, it will be a good failover fix.

Please link to any relevant issues, pull requests, or other discussions.

https://saas.transistor.fm/episodes/worst-day-ever

Reuse certificates already loaded into memory

What would you like to have changed?

Currently, every time an in-memory certificate cache is replaced (for example, when an importer of CertMagic such as Caddy changes its config), all certificates are loaded from storage again to populate the new cache. This loading and decoding takes both time and memory (although the old cache is garbage-collected eventually).

First, careful profiling should be done on a server with thousands of certs to ensure this is actually a CPU/memory bottleneck.

Then, if it is, we should invent a way to transfer one in-memory certCache to a new one, or to allow the new one to draw from the old cache. I do not know how to do this, though, because in order to know if a certificate was changed in storage, we'd have to load and parse it, which ... we might as well just use it at that point.

Why is this feature a useful, necessary, and/or important addition to this project?

For deployments with thousands of certificates, the present implementation is not ideal as it requires more CPU cores and memory to keep running quickly and smoothly.

What alternatives are there, or what are you doing in the meantime to work around the lack of this feature?

Get better hardware, I guess. Honestly, this is not usually too big of a problem: it really affects the 1-2 core machines with 512 MB (or less) of memory the most, and upgrading those is very inexpensive anyway, we're talking about like $5/mo. to $10 or $15/mo.

But, in the effort of improving our efficiency over time, this is something worth looking into.

CLI tool plan?

Hi Matt, Thanks for great libarary !

Is it possible to add a cli to wrap it to command line interface?

Wildcard certificate is not loaded from storage

What version of the package are you using?

github.com/caddyserver/certmagic v0.11.0

What are you trying to do?

I'm attempting to setup a cluster configuration where go process do the TLS termination and share a common storage. However (and I suspect the problem start here), the certificates creation is done in another process connected to the same storage, by calling config.ManageSync() and exiting.

(On a side note, it'd be nice if a lower level function would be exposed to check/generate/renew certificates without the higher level stuff that ManageSync imply).

What steps did you take?

After making sure that a wildcard certificate exist for *.example.com, I did a HTTPS request to foo.example.com on one of the nodes connected to the common storage.

Following what happen with a debugger, it seems to me that the in-memory cache is checked first. This fail as the node doesn't have any certificates at that point.

As a fallback, the storage is then checked. However, only the exact match for foo.example.com is attempted.

(Side note again, an OnDemand config needs to be created for that to work. However, I do not want to generate new certificates, only load them from the disk.)

What did you expect to happen, and what actually happened instead?

I expected my wildcard certificates to be loaded.

How do you think this should be fixed?

I would think the storage should be checked for both foo.example.com and *.example.com. Possibly in parallel.

Bonus: What do you use CertMagic for, and do you find it useful?

Because it's the only way I could find to solve my problem. That's no small feat.

Need help incorporating it in application

What is your question?

I have a type the following code but finding it hard to integrate certmagic

type Server struct {
  Port        int
  Addr        string
  HTTPServer  *http.Server
}

func (s *Server) Start(){
  s.HTTPServer.ListenAndServe()
}

I set values for http.Server later on with (addr and handler) so I can just call http.ListenAndServe(), however certmagic.HTTPS() seems to require two arguments. Am I able to call certmagic and use the default addr, handler etc?

What have you already tried?

I tried just dropping certmagic.HTTPS([]string{"example.com"}) but this throws an error. There doesn't seem to any other high level call to just drop in.

Include any other information or discussion.

Bonus: What do you use this package for, and does it help you?

Hoping to use it for an API

List of known storage implementations

Hi,

Do you plan on maintaining a list of known Storage implementations? While I could empathise with you potentially not wanting to maintain a plethora of community-contributed implementations, it might be helpful to maintain a list of known implementations out in the wild, to dedupe effort and get people up and running faster.

My own selfish desire for this is borne out of hoping that someone else has already implemented an AWS DynamoDB implementation -- but it's a bit hard to find anything on Google.

(Btw, Fantastic project - love your work)

Configurable Logging

There are various locations where log.Printf I used to log errors, warnings and info messages. Since certmagic is used as a library one should be able to provide a custom logging interface if the logging to Stderr is not desired.

Similar to the already existing OnEvent(string, interface{}) callback I would propose the addition of the OnError(error) and OnInfo(string) callbacks. The default interface would log the errors to Stderr and info messages to Stdout.

I'm happy to put together a pull request once we agree on the final interface. 🚀

certmagic built failed for FileStorage not implement enough methods

What version of the package are you using?

commit: c1d472b

What are you trying to do?

Upgrade caddy in coredns to 0.11.2

What steps did you take?

Try to init cluster plugin like following:

// Any flags defined here, need to be namespaced to the serverType other
// wise they potentially clash with other server types.
func init() {
	flag.StringVar(&Port, serverType+".port", DefaultPort, "Default port")

	caddy.RegisterServerType(serverType, caddy.ServerType{
		Directives: func() []string { return Directives },
		DefaultInput: func() caddy.Input {
			return caddy.CaddyfileInput{
				Filepath:       "Corefile",
				Contents:       []byte(".:" + Port + " {\nwhoami\n}\n"),
				ServerTypeName: serverType,
			}
		},
		NewContext: newContext,
	})
	// ensure the default Storage implementation is plugged in
	caddy.RegisterClusterPlugin("file", constructDefaultClusterPlugin)
}

func constructDefaultClusterPlugin() (certmagic.Storage, error) {
	return certmagic.FileStorage{Path: caddy.AssetsPath()}, nil
}

What did you expect to happen, and what actually happened instead?

Expect to build seccessfully, but met:

# github.com/coredns/coredns/core/dnsserver
../core/dnsserver/register.go:39:30: cannot use constructDefaultClusterPlugin (type func() ("github.com/mholt/certmagic".Storage, error)) as type caddy.ClusterPluginConstructor in argument to caddy.RegisterClusterPlugin
../core/dnsserver/register.go:47:30: cannot use "github.com/mholt/certmagic".FileStorage literal (type "github.com/mholt/certmagic".FileStorage) as type "github.com/mholt/certmagic".Storage in return argument:
	"github.com/mholt/certmagic".FileStorage does not implement "github.com/mholt/certmagic".Storage (Delete method has pointer receiver)

How do you think this should be fixed?

Implement Lock, Unlock, Store for FileStorage.

Please link to any related issues, pull requests, and/or discussion

a3b276a

Bonus: What do you use CertMagic for, and do you find it useful?

I do not use CertMagic directly, coredns needs caddy, and caddy needs CertMagic.

Test suite for storage implementations

Hi,

I would love to write a DynamoDb certmagic.Storage implementation (as per #41) - and have done so. What I'm not confident about is whether it correctly implements the interface, e.g. in terms of respective the recursive parameter for List() etc.

Is there a suite of tests that I can run against my implementation to make sure it is correct?

Thank you for such a wonderful package.

EDIT: The work in progress is here. I haven't implemented locking, etc yet. But might be helpful for discussion purposes https://github.com/glassechidna/awscertmagic/blob/master/dynamodb.go

add selfSignedCertificate

What would you like to have changed?

it would be usefull to have the "newSelfSignedCertificate" code migrated from caddy to certmagic.

Why is this feature a useful, necessary, and/or important addition to this project?

In order to test applications in local docker env's.
requesting, creating, signing and managing a selfsigned for an application is still pain and needs some more magic!

I think a good solution would be to have something simular like certmagic.HTTPS() which creates and uses a selfsigned

Does certmagic have any limitations?

e.g. 50 certs per hour limit or something with letsencrypt?
i'm running web hosting services and will be hosting ... (hopefully) millions of domain names.

will there be any issues per server of deployment etc?

Can't delete old OCSP staples: no such file or directory

What version of the package are you using?

9645f8e

What are you trying to do?

I'm trying to integrate CertMagic into my own project (see below for description).

What steps did you take?

I constructed a new instance of CertMagic with a custom Cache, backed by a custom FileStorage storage. I did because I wanted to customize the folder which is used to store data fetched/generated by CertMagic.

(I can also see this behaviour when running "vanilla" as described in the readme, e.g. without specifying a custom cache or storage)

What did you expect to happen, and what actually happened instead?

Expected

I expected the stale OCSP staples routine to find the expected files in the filesystem.

Observed

2018/12/12 20:42:17 [INFO] Scanning for stale OCSP staples
2018/12/12 20:42:17 [ERROR] While deleting old OCSP staples, unable to load staple file: open /Users/evenh/.intercert/server-data/foo.koderiet.org-812f94e6: no such file or directory

How do you think this should be fixed?

By ensuring that the routine looks in the ocsp subfolder of the data directory (not in the root) used by the Storage.

Please link to any related issues, pull requests, and/or discussion

N/A

Bonus: What do you use CertMagic for, and do you find it useful?

I'm trying to create a little program that can run somewhere with an internet connection and requests DNS-validated certs on behalf of clients running in a locked down LAN.

Add support to DynamoDB as a cluster storage backend

What would you like to have changed?

Add support for DynamoDB as a cluster storage backend

Why is this feature a useful, necessary, and/or important addition to this project?

IMO, it allows people that don't want to manage a Consul cluster or shared filesystem to get the clustering feature on top of DynamoDB, which does not require much effort.

What alternatives are there, or what are you doing in the meantime to work around the lack of this feature?

Right now we depend on Consul being fully HA in order to get Caddy in a "clustered" way. Having it in DynamoDB will give us more peace of mind since we don't need to care about Consul's availability or any shared filesystems alternative to have a healthy Caddy running.

Please link to any relevant issues, pull requests, or other discussions.

I can't think of one at this moment

How to do graceful shutdown of server?

What is your question?

As of Go 1.8, the http server from the std lib has a Shutdown() method to gracefully stop the server. Is there anything similar for the certmagic server?

What have you already tried?

Looking in the godoc's for any .Stop() or .Shutdown() methods.

Include any other information or discussion.

Here's an article that shows how this is done with the http server from Go: https://hackernoon.com/how-to-safely-shut-down-a-server-in-go-aec16dfbc57f

Bonus: What do you use this package for, and does it help you?

Building a production website that hopefully will use this to keep its certs current.

Matching certificate by IP address

The comment in handshake.go says if SNI is empty, prefer matching IP address

https://github.com/caddyserver/certmagic/blob/master/handshake.go#L113-L124

How am I supposed to associate certificate with IP address (or domain?)?

My situation is that hello.ServerName contains IP address so setting DefaultServerName does not help. I guess I should use CertSelection but perhaps there are better options? The error I get is

no certificate available for 'XXX.XXX.XXX.XXX'

where XXX.XXX.XXX.XXX is an IP address for my domain.

Use a custom CSR

What is your question?

I'm attempting to create a certificate using LetsEncrypt and DNS01 challenge with DNSimple. I would like to provide my own Certificate Signing Request (CSR) rather than having the library create its own. How can I do this?

What have you already tried?

I've looked at the codebase and I believe this is something that is delegated to the go-acme library, but I don't see any clear way of configuring it.

HTTP -> HTTPS redirect not working

I currently have a domain name, lets just call example.com. When I open an incognito tab and enter just example.com I get the message [ip of my droplet] returned invalid response. However if I type https://example.com into the search bar, then my website loads.

I went into certmagic.go to and printed out the url that it was trying to redirect to, and its https://[ip of my droplet].

Is this a problem with my DNS setup?

Currently I have example.com to redirect to a masked ip, and that ip is http://[server ip]:80. I also have an A record pointing at my droplet ip.

Sorry if this a dumb question, I'm pretty new to web dev.

Export Cache instance as part of Config type

What would you like to have changed?

I would like to expose the Cache instance that is part of the Config type.

Why is this feature a useful, necessary, and/or important addition to this project?

The Cache instance has two methods Stop and RenewManagedCertificates. As part of a Graceful Shutdown, I would like to be able to close the cache renewal loop properly.

What alternatives are there, or what are you doing in the meantime to work around the lack of this feature?

I saw in the caddy sources that you register the call to Close with an event handler. However, in my current implementation, the creation of the cache instance is part of the creation of a new Config type and is out of scope of my main type Server that manage graceful shutdown.

Wildcard domains (*.example.com)

Would like to have wildcard domain support, right now certificates are issued for every subdomain i.e
1234.example.com
43443.example.com
have separate certificates.

We should create 1 certificate with *.example.com, and use it instead for every subdomain.

This would be helpful for services which serve subdomains.

Can start working on this if i have your ok @mholt

Support for multi-SAN certificate ?

I'm currently assessing CertMagic to see if it could solve the problem we have for a project. So far it looks great.

Due to the limitation that wildcard certificate can only cover a single level of subdomain and not the domain itself, we are in a situation where we would need to generate a non-trivial number of certificates. Being able to generate (by using lower level functions) certificates with a bunch of domains at once would definitely help regarding the LetsEncrypt rate limiting.

Reading the code, it looks to me that CertMagic assume in a few places that managed certificate have a single SAN, notably during renewal. Is that correct ?

How hard would it be to allow generation and renewal of multi-SAN certificate ? Would it be something you agree to have in your code-base ?

*tls.ClientHelloInfo has no field or method SupportsCertificate

What version of the package are you using?

v0.10.12

What are you trying to do?

What steps did you take?

What did you expect to happen, and what actually happened instead?

go version 1.13.8 func hello.SupportsCertificate undefined, required go version 1.14.2
golang/go@dd01738

# github.com/caddyserver/certmagic
../../../../../pkg/mod/github.com/caddyserver/[email protected]/handshake.go:201:18: hello.SupportsCertificate undefined (type *tls.ClientHelloInfo has no field or method SupportsCertificate)

How do you think this should be fixed?

Please link to any related issues, pull requests, and/or discussion

Bonus: What do you use CertMagic for, and do you find it useful?

Can HTTPS be exposed directly to the internet?

What would you like to have changed?

More of a feature request/question.

I'm curious if calling certmagic.HTTPS without a proxy in front of it is safe. Cloudflare has a (dated) post here on config details for exposing a go server to the public internet.

My services have always sat behind a proxy of sorts and I'm interested in just exposing to the internet.

I know caddy uses this library, but does it use custom configurations?

Why is this feature a useful, necessary, and/or important addition to this project?

It would allow a go service to be exposed to the internet without a proxy in front of it.

What alternatives are there, or what are you doing in the meantime to work around the lack of this feature?

My services (go or other) sit behind nginx

'panic: close of closed channel' using simple example code

What version of the package are you using?

v0.10.7

What are you trying to do?

Run example code

import (
        "net/http"
        "github.com/caddyserver/certmagic"
        "fmt"
        "log"

)


func main() {
        http.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {
                fmt.Fprintf(w, "Hello world")
        })

        err := certmagic.HTTPS([]string{"tmp.mydomain.com"}, nil)
        if err != nil {
                log.Fatal(err)
        }
}

What steps did you take?

  1. On tmp.mydomain.com server, removed .local/share/certmagic directory (to start fresh)
  2. Built code locally, scp'd to tmp.mydomain.com
  3. On tmp.mydomain.com, performed: setcap 'cap_net_bind_service=+ep' ./autotest3 && ./autotest3

What did you expect to happen, and what actually happened instead?

Sometimes the example code works, sometimes it panics like this:

2020/04/05 00:21:57 [INFO][tmp.mydomain.com] Obtain certificate; acquiring lock...
2020/04/05 00:21:57 [INFO][tmp.mydomain.com] Obtain: Lock acquired; proceeding...
2020/04/05 00:21:57 [INFO] acme: Registering account for [email protected]
2020/04/05 00:21:57 [INFO][tmp.mydomain.com] Waiting on rate limiter...
2020/04/05 00:21:57 [INFO][tmp.mydomain.com] Done waiting
2020/04/05 00:21:57 [INFO] [tmp.mydomain.com] acme: Obtaining bundled SAN certificate given a CSR
2020/04/05 00:21:57 [INFO] [tmp.mydomain.com] AuthURL: https://acme-v02.api.letsencrypt.org/acme/authz-v3/3765978543
2020/04/05 00:21:57 [INFO] [tmp.mydomain.com] acme: use tls-alpn-01 solver
2020/04/05 00:21:57 [INFO] [tmp.mydomain.com] acme: Trying to solve TLS-ALPN-01
2020/04/05 00:21:58 [INFO][tmp.mydomain.com] Served key authentication certificate (TLS-ALPN challenge)
2020/04/05 00:21:58 [INFO][tmp.mydomain.com] Served key authentication certificate (TLS-ALPN challenge)
2020/04/05 00:21:58 [INFO][tmp.mydomain.com] Served key authentication certificate (TLS-ALPN challenge)
2020/04/05 00:21:58 [INFO][tmp.mydomain.com] Served key authentication certificate (TLS-ALPN challenge)
2020/04/05 00:22:04 [INFO] [tmp.mydomain.com] The server validated our request
panic: close of closed channel
        panic: close of closed channel
        panic: close of closed channel
        panic: close of closed channel

goroutine 29 [running]:
github.com/caddyserver/certmagic.(*tlsALPNSolver).Present.func1(0xc0000f5da0, 0xc000326cc0)
        /home/x/go/pkg/mod/github.com/caddyserver/[email protected]/solvers.go:155 +0x177
created by github.com/caddyserver/certmagic.(*tlsALPNSolver).Present
        /home/x/go/pkg/mod/github.com/caddyserver/[email protected]/solvers.go:149 +0x709

How do you think this should be fixed?

Out of curiosity, I built the code both with the datarace detector enabled and disabled, and I can reproduce the issue (intermittently) in both cases.

Disable certificate renewal

What would you like to have changed?

Ability to disable certificate renewal.

Why is this feature a useful, necessary, and/or important addition to this project?

I'm using the dns challenge since I need wildcard certificates, the problem is that every server needs to know the credentials for my dns provider, which I find unnecessary since I could have a single server do the renewal, or something hosted on some serverless platform, so because of this it would be awesome to have the ability to disable renewal on the rest of the servers.

What alternatives are there, or what are you doing in the meantime to work around the lack of this feature?

I'm setting up a dns provider but passing in invalid credentials, which will probably become a problem when certmagic will decide it's time to renew the certificates.

ACME Manager does not support CA URLs with non-standard port

What version of the package are you using?

from caddy-v2.0.0-rc.3

What are you trying to do?

I am trying to use a local ca and ACME server (smallstep) to provide certs to Caddy.
The local CA ACME webserver runs on port 9000 instead of the https port inside my Dev environment.

What steps did you take?

I added the URL https://mike-lab.private:9000/acme/acme/directory which is passed from caddy to certmagic.

What did you expect to happen, and what actually happened instead?

I expected caddy to attempt to contact the ACME server on port 9000, instead I got a golang error because the URL includes a port: "first path segment in URL cannot contain colon".

How do you think this should be fixed?

Investigate whether am.issuerKey should be enhanced to allow ports within a URL, since the golang url.parse does not support this.

Please link to any related issues, pull requests, and/or discussion

golang/go#19297

version 0.9.1 regression causes a systematic crash (rolled back to 0.8.3 to fix)

What version of the package are you using?

0.9.1

What are you trying to do?

We use successfully Certmagic in production since 10 months.

		certmagic.Default.Agreed = true
		certmagic.Default.Email = s.Email
		certmagic.Default.DisableHTTPChallenge = true

		cfg := certmagic.NewDefault()
		tlsConfig := cfg.TLSConfig()
		err := cfg.ManageSync(s.Hosts)
		if err != nil {
			return err
		}

What steps did you take?

We just have updated to certmagic 0.9.0 (crashes happened regularly)
With certmagic 0.9.1 panic is systematic
So we have rolled back to 0.8.3 that is stable.

2020/01/12 08:44:21 [INFO][cache:0xc000189720] Started certificate maintenance routine
2020/01/12 08:44:22 [INFO][0.tplst.com] Renew certificate
2020/01/12 08:44:22 [INFO][0.tplst.com] Renew: Waiting on rate limiter...
2020/01/12 08:44:22 [INFO][0.tplst.com] Renew: Done waiting
2020/01/12 08:44:22 [INFO] [0.tplst.com] acme: Trying renewal with 710 hours remaining
2020/01/12 08:44:22 [INFO] [0.tplst.com] acme: Obtaining bundled SAN certificate
panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x28 pc=0x7667a5]

goroutine 1 [running]:
time.(*Timer).Stop(...)
	/usr/local/go/src/time/sleep.go:74
github.com/cenkalti/backoff/v3.(*defaultTimer).Stop(0xc00059c120)
	/home/tplst/go/pkg/mod/github.com/cenkalti/backoff/[email protected]/timer.go:32 +0x25
github.com/cenkalti/backoff/v3.RetryNotifyWithTimer.func1(0xc1e9a0, 0xc00059c120)
	/home/tplst/go/pkg/mod/github.com/cenkalti/backoff/[email protected]/retry.go:45 +0x31
github.com/cenkalti/backoff/v3.RetryNotifyWithTimer(0xc000498420, 0x7f1daeca15e8, 0xc0004f7cc0, 0x0, 0xc1e9a0, 0xc00059c120, 0x0, 0x0)
	/home/tplst/go/pkg/mod/github.com/cenkalti/backoff/[email protected]/retry.go:53 +0x34d
github.com/cenkalti/backoff/v3.RetryNotify(...)
	/home/tplst/go/pkg/mod/github.com/cenkalti/backoff/[email protected]/retry.go:31
github.com/cenkalti/backoff/v3.Retry(...)
	/home/tplst/go/pkg/mod/github.com/cenkalti/backoff/[email protected]/retry.go:25
github.com/go-acme/lego/v3/acme/api.(*Core).retrievablePost(0xc0003e6000, 0xc0006f6500, 0x33, 0xc000730e40, 0x36, 0x40, 0xa09320, 0xc0003b63c0, 0xc000144610, 0xc0003b6460, ...)
	/home/tplst/go/pkg/mod/github.com/go-acme/lego/[email protected]/acme/api/api.go:107 +0x210
github.com/go-acme/lego/v3/acme/api.(*Core).post(0xc0003e6000, 0xc0006f6500, 0x33, 0xaebde0, 0xc0003b6460, 0xa09320, 0xc0003b63c0, 0x1, 0x200000003, 0xc000000180)
	/home/tplst/go/pkg/mod/github.com/go-acme/lego/[email protected]/acme/api/api.go:70 +0xf5
github.com/go-acme/lego/v3/acme/api.(*OrderService).New(0xc0003e60c0, 0xc0006db9c0, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, ...)
	/home/tplst/go/pkg/mod/github.com/go-acme/lego/[email protected]/acme/api/order.go:22 +0x240
github.com/go-acme/lego/v3/certificate.(*Certifier).Obtain(0xc0006e5200, 0xc0006db9b0, 0x1, 0x1, 0x1, 0xabacc0, 0xc000738e40, 0x0, 0xc000086958, 0x0, ...)
	/home/tplst/go/pkg/mod/github.com/go-acme/lego/[email protected]/certificate/certificates.go:102 +0x1ec
github.com/go-acme/lego/v3/certificate.(*Certifier).Renew(0xc0006e5200, 0xc00072e250, 0xb, 0xc000086180, 0x53, 0xc0000861e0, 0x53, 0xc00074a000, 0xe3, 0x2e3, ...)
	/home/tplst/go/pkg/mod/github.com/go-acme/lego/[email protected]/certificate/certificates.go:384 +0x427
github.com/mholt/certmagic.(*acmeClient).tryRenew(0xc0006e5230, 0xc00072e250, 0xb, 0xc000086180, 0x53, 0xc0000861e0, 0x53, 0xc00074a000, 0xe3, 0x2e3, ...)
	/home/tplst/go/pkg/mod/github.com/mholt/[email protected]/client.go:419 +0x80
github.com/mholt/certmagic.(*acmeClient).Renew(0xc0006e5230, 0xc214a0, 0xc000026158, 0x7fff3ff07516, 0xb, 0x0, 0x0)
	/home/tplst/go/pkg/mod/github.com/mholt/[email protected]/client.go:394 +0x624
github.com/mholt/certmagic.(*Config).RenewCert(0xc0003e60f0, 0xc214a0, 0xc000026158, 0x7fff3ff07516, 0xb, 0xc0000b2400, 0x20f, 0x40f)
	/home/tplst/go/pkg/mod/github.com/mholt/[email protected]/config.go:478 +0x183
github.com/mholt/certmagic.(*Config).manageOne(0xc0003e60f0, 0xc214a0, 0xc000026158, 0x7fff3ff07516, 0xb, 0x2, 0x8)
	/home/tplst/go/pkg/mod/github.com/mholt/[email protected]/config.go:426 +0x4f1
github.com/mholt/certmagic.(*Config).manageAll(0xc0003e60f0, 0x0, 0x0, 0xc0000ab320, 0x12, 0x12, 0x7f1daeced000, 0x0, 0xc000459180)
	/home/tplst/go/pkg/mod/github.com/mholt/[email protected]/config.go:394 +0x1d1
github.com/mholt/certmagic.(*Config).ManageSync(...)
	/home/tplst/go/pkg/mod/github.com/mholt/[email protected]/config.go:320
gitlab.tplst.com/theplaylist/tplst/pkg/com.(*HttpServer).Serve(0xc000499c48, 0xc139c0, 0xc000093a00, 0x0, 0xc0000ab320)
	/home/tplst/src/pkg/com/http_server.go:30 +0xd5
gitlab.tplst.com/theplaylist/tplst/pkg/cmds.(*Tplst).start(0xc000093a00, 0xc000093a00, 0x7fff3ff0750f)
	/home/tplst/src/pkg/cmds/tplst.go:158 +0xbe7
gitlab.tplst.com/theplaylist/tplst/pkg/cmds.NewTplst(...)
	/home/tplst/src/pkg/cmds/tplst.go:60
main.main()
	/home/tplst/src/tplst.go:85 +0x45e

Is "github.com/cenkalti/backoff" really required?

cap_net_bind_service=+ep and go run main together?

What is your question?

How do you develop with certmagic when you need a debugger? I'd like to continue using port 80/443 /w self signed certs in VS Code without running as root.

Basically how to combine go run /path/to/main.go with cap_net_bind_service=+ep?

What have you already tried?

I can build the binary and run sudo setcap cap_net_bind_service=+ep /path/to/your/binary which works but the debugger is a bit flaky when using a remote binary.

I can also run as root (but would prefer not to)

Include any other information or discussion.

This question is rather broad and I apologize ahead of time. I figured the likelihood of the authors here being able to help me would be very high.

Edit: If we can find a solution, I volunteer to write a little section in the readme on using certmagic locally with self signed certificates for development purposes 😄

Bonus: What do you use this package for, and does it help you?

Currently unannounced project that uses certmagic (especially it's on-demand TLS feature). This library is absolutely amazing. Thanks a ton!!

Enhance locking and avoid queue pileups in rare case of widespread, permanent errors

In rare circumstances (which have been observed), it is possible that CertMagic can gradually leak goroutines & memory by piling up a renewal queue if there are persistent errors for a lot of the obtain/renewal operations in a call to ManageAsync.

This is because the exponential backoff can span up to a month, and renewal checks are done every 12 hours by the maintenance goroutine, but the two don't coordinate: the maintenance goroutine has no idea that ManageAsync is still trying to renew. With the internal rate limiter and exponential backoff, it seems that it is possible for duplicate operations to pile up unnecessarily.

Either add some coordination so that cert ops are de-duplicated within the process, or adjust locking via storage so that locks are longer-lived, through errors and retries (but this will require us to put a liveliness timestamp in the lockfile, actively updating it every so often, as opposed to our current lazy method).

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.