From 723c17fdd74ee63455e40df46edd11eb34f98663 Mon Sep 17 00:00:00 2001 From: Jake Sanders Date: Fri, 11 Oct 2019 16:25:15 +0100 Subject: [PATCH] Implementation of certmagic as an ACME provider --- api/server/acme/acme.go | 69 +++++++++++++++++++++ api/server/acme/autocert/autocert.go | 3 +- api/server/acme/certmagic/certmagic.go | 42 +++++++++++++ api/server/acme/certmagic/certmagic_test.go | 37 +++++++++++ 4 files changed, 150 insertions(+), 1 deletion(-) create mode 100644 api/server/acme/certmagic/certmagic.go create mode 100644 api/server/acme/certmagic/certmagic_test.go diff --git a/api/server/acme/acme.go b/api/server/acme/acme.go index 0bf3698d..9d61816a 100644 --- a/api/server/acme/acme.go +++ b/api/server/acme/acme.go @@ -4,9 +4,13 @@ package acme import ( "errors" "net" + + "github.com/go-acme/lego/v3/challenge" ) var ( + // ErrProviderNotImplemented can be returned when attempting to + // instantiate an unimplemented provider ErrProviderNotImplemented = errors.New("Provider not implemented") ) @@ -14,3 +18,68 @@ var ( type Provider interface { NewListener(...string) (net.Listener, error) } + +// The Let's Encrypt ACME endpoints +const ( + LetsEncryptStagingCA = "https://acme-staging-v02.api.letsencrypt.org/directory" + LetsEncryptProductionCA = "https://acme-v02.api.letsencrypt.org/directory" +) + +// Option (or Options) are passed to New() to configure providers +type Option func(o *Options) + +// Options represents various options you can present to ACME providers +type Options struct { + // AcceptTLS must be set to true to indicate that you have read your + // provider's terms of service. + AcceptToS bool + // CA is the CA to use + CA string + // ChallengeProvider is a go-acme/lego challenge provider. Set this if you + // want to use DNS Challenges. Otherwise, tls-alpn-01 will be used + ChallengeProvider challenge.Provider + // Issue certificates for domains on demand. Otherwise, certs will be + // retrieved / issued on start-up. + OnDemand bool + // TODO + Cache interface{} +} + +// AcceptTLS indicates whether you accept your CA's terms of service +func AcceptTLS(b bool) Option { + return func(o *Options) { + o.AcceptToS = b + } +} + +// CA sets the CA of an acme.Options +func CA(CA string) Option { + return func(o *Options) { + o.CA = CA + } +} + +// ChallengeProvider sets the Challenge provider of an acme.Options +// if set, it enables the DNS challenge, otherwise tls-alpn-01 will be used. +func ChallengeProvider(p challenge.Provider) Option { + return func(o *Options) { + o.ChallengeProvider = p + } +} + +// OnDemand enables on-demand certificate issuance. Not recommended for use +// with the DNS challenge, as the first connection may be very slow. +func OnDemand(b bool) Option { + return func(o *Options) { + o.OnDemand = b + } +} + +// Default uses the Let's Encrypt Production CA, with DNS Challenge disabled. +func Default() []Option { + return []Option{ + AcceptTLS(true), + CA(LetsEncryptProductionCA), + OnDemand(true), + } +} diff --git a/api/server/acme/autocert/autocert.go b/api/server/acme/autocert/autocert.go index 9a760baf..ad5bf0bd 100644 --- a/api/server/acme/autocert/autocert.go +++ b/api/server/acme/autocert/autocert.go @@ -1,4 +1,5 @@ -// Package autocert is the ACME interpreter from golang.org/x/crypto/acme/autocert +// Package autocert is the ACME provider from golang.org/x/crypto/acme/autocert +// This provider does not take any config. package autocert import ( diff --git a/api/server/acme/certmagic/certmagic.go b/api/server/acme/certmagic/certmagic.go new file mode 100644 index 00000000..68e36f64 --- /dev/null +++ b/api/server/acme/certmagic/certmagic.go @@ -0,0 +1,42 @@ +// Package certmagic is the ACME provider from github.com/mholt/certmagic +package certmagic + +import ( + "net" + + "github.com/mholt/certmagic" + + "github.com/micro/go-micro/api/server/acme" +) + +type certmagicProvider struct { + opts *acme.Options +} + +func (c *certmagicProvider) NewListener(ACMEHosts ...string) (net.Listener, error) { + if c.opts.ChallengeProvider != nil { + // Enabling DNS Challenge disables the other challenges + certmagic.Default.DNSProvider = c.opts.ChallengeProvider + } + if c.opts.OnDemand { + certmagic.Default.OnDemand = new(certmagic.OnDemandConfig) + } + return certmagic.Listen(ACMEHosts) +} + +// New returns a certmagic provider +func New(options ...acme.Option) acme.Provider { + o := &acme.Options{} + if len(options) == 0 { + for _, op := range acme.Default() { + op(o) + } + } else { + for _, op := range options { + op(o) + } + } + return &certmagicProvider{ + opts: o, + } +} diff --git a/api/server/acme/certmagic/certmagic_test.go b/api/server/acme/certmagic/certmagic_test.go new file mode 100644 index 00000000..5aeceb0b --- /dev/null +++ b/api/server/acme/certmagic/certmagic_test.go @@ -0,0 +1,37 @@ +package certmagic + +import ( + "testing" + + "github.com/go-acme/lego/v3/providers/dns/cloudflare" + "github.com/micro/go-micro/api/server/acme" +) + +func TestCertMagic(t *testing.T) { + l, err := New().NewListener() + if err != nil { + t.Error(err.Error()) + } + l.Close() + + c := cloudflare.NewDefaultConfig() + c.AuthEmail = "" + c.AuthKey = "" + c.AuthToken = "test" + c.ZoneToken = "test" + + p, err := cloudflare.NewDNSProviderConfig(c) + if err != nil { + t.Error(err.Error()) + } + + l, err = New(acme.AcceptTLS(true), + acme.CA(acme.LetsEncryptStagingCA), + acme.ChallengeProvider(p), + ).NewListener() + + if err != nil { + t.Error(err.Error()) + } + l.Close() +}