acmewrapper

package module
v0.0.0-...-1fd8bb1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Mar 19, 2016 License: MIT Imports: 17 Imported by: 0

README

ACMEWrapper

Add Let's Encrypt support to your golang server in 10 lines of code.

GoDoc

w, err := acmewrapper.New(acmewrapper.Config{
	Domains: []string{"example.com","www.example.com"},
	Address: ":443",

	TLSCertFile: "cert.pem",
	TLSKeyFile:  "key.pem",

	// Let's Encrypt stuff
	RegistrationFile: "user.reg",
	PrivateKeyFile:   "user.pem",

	TOSCallback: acmewrapper.TOSAgree,
})


if err!=nil {
	log.Fatal("acmewrapper: ", err)
}

listener, err := tls.Listen("tcp", ":443", w.TLSConfig())

Acmewrapper is built upon https://github.com/xenolf/lego, and handles all certificate generation, renewal and replacement automatically. After the above code snippet, your certificate will automatically be renewed 30 days before expiring without downtime. Any files that don't exist will be created, and your "cert.pem" and "key.pem" will be kept up to date.

Since Let's Encrypt is usually an option that can be turned off, the wrapper allows disabling ACME support and just using normal certificates, with the bonus of allowing live reload (ie: change your certificates during runtime).

And finally, technically, none of the file names shown above are actually necessary. The only needed fields are Domains and TOSCallback. Without the given file names, acmewrapper runs in-memory. Beware, though: if you do that, you might run into rate limiting from Let's Encrypt if you restart too often!

WARNING: This code literally JUST started working. Do NOT use it anywhere important before it has some time to mature.

How It Works

Let's Encrypt has SNI support for domain validation. That means we can update our certificate if we control the TLS configuration of a server. That is exactly what acmewrapper does. Not only does it transparently update your server's certificate, but it uses its control of SNI to pass validation tests.

This means that no other changes are needed to your code. You don't need any special handlers or hidden directories. So long as acmewrapper is able to set your TLS configuration, and your TLS server is running on port 443, you can instantly have a working Let's Encrypt certificate.

NOTE: The ability to manage certificates in this way was enabled in go 1.6 - acmewrapper will not work with older go versions.

Example

You can go into ./example to find a sample basic http server that will serve a given folder over https with Let's Encrypt.

Another simple example is given below:

Old Code

This is sample code before adding Let's Encrypt support:

package main

import (
    "io"
    "net/http"
    "log"
)

func HelloServer(w http.ResponseWriter, req *http.Request) {
    io.WriteString(w, "hello, world!\n")
}

func main() {
    http.HandleFunc("/hello", HelloServer)
    err := http.ListenAndServeTLS(":443", "cert.pem", "key.pem", nil)
    if err != nil {
        log.Fatal("ListenAndServe: ", err)
    }
}
New Code

Adding let's encrypt support is a matter of setting the tls config:

package main

import (
    "io"
    "net/http"
    "log"
	"crypto/tls"

	"github.com/dkumor/acmewrapper"
)

func HelloServer(w http.ResponseWriter, req *http.Request) {
    io.WriteString(w, "hello, world!\n")
}

func main() {
	mux := http.NewServeMux()
    mux.HandleFunc("/hello", HelloServer)

	w, err := acmewrapper.New(acmewrapper.Config{
		Domains: []string{"example.com","www.example.com"},
		Address: ":443",

		TLSCertFile: "cert.pem",
		TLSKeyFile:  "key.pem",

		RegistrationFile: "user.reg",
		PrivateKeyFile:   "user.pem",

		TOSCallback: acmewrapper.TOSAgree,
	})


	if err!=nil {
		log.Fatal("acmewrapper: ", err)
	}

	tlsconfig := w.TLSConfig()

	listener, err := tls.Listen("tcp", ":443", tlsconfig)
    if err != nil {
        log.Fatal("Listener: ", err)
    }

	// To enable http2, we need http.Server to have reference to tlsconfig
	// https://github.com/golang/go/issues/14374
	server := &http.Server{
		Addr: ":443",
		Handler:   mux,
		TLSConfig: tlsconfig,
	}
	server.Serve(listener)
}

Testing

Running the tests is a bit of a chore, since it requires a valid domain name, and access to port 443. This is because ACMEWrapper uses the Let's Encrypt staging server to make sure the code is working.

To test on your own server, you need to change the domain name to your domain, and set a custom testing port that will be routed to 443:

sudo iptables -t nat -A PREROUTING -p tcp --dport 443 -j REDIRECT --to-port 8443
export TLSADDRESS=":8443"
export DOMAIN_NAME="example.com"
go test

To delete the port forwarding rule, find it in your tables

sudo iptables -t nat --line-numbers -n -L

And delete the number that it was at

iptables -t nat -D PREROUTING 2

(This is based on http://serverfault.com/questions/112795/how-can-i-run-a-server-on-linux-on-port-80-as-a-normal-user)

Documentation

Index

Constants

View Source
const (
	// The server to use by default
	DefaultServer = "https://acme-v01.api.letsencrypt.org/directory"
	// Default type for the private key
	DefaultKeyType = acme.RSA2048

	// The default port to use for initializing certs on startup
	DefaultAddress = ":443"

	// DefaultRenewTime is the time period before cert expiration to attempt renewal
	DefaultRenewTime  = int64(60 * 60 * 24 * 30)
	DefaultRetryDelay = int64(60 * 60 * 24 * 1) // Retry once a day
	DefaultRenewCheck = int64(60 * 60 * 12)     // The time between checks for renewal
)

Variables

This section is empty.

Functions

func LoadPrivateKey

func LoadPrivateKey(filename string) (crypto.PrivateKey, error)

LoadPrivateKey reads a key from file This code copied verbatim from caddy: https://github.com/mholt/caddy/blob/master/caddy/https/crypto.go

func SavePrivateKey

func SavePrivateKey(filename string, key crypto.PrivateKey) error

SavePrivateKey is used to write the given key to file This code copied verbatim from caddy: https://github.com/mholt/caddy/blob/master/caddy/https/crypto.go

func TOSAgree

func TOSAgree(agreementURL string) bool

TOSAgree always agrees to the terms of service. This should only be really used if you realize that you could be selling your soul without being notified.

func TOSDecline

func TOSDecline(agreementURL string) bool

TOSDecline always declines to the terms of service. This can be usd for testing, when you want to make sure that ACME is really off, or that the user is being loaded.

Types

type AcmeWrapper

type AcmeWrapper struct {
	sync.Mutex
	Config Config
	// contains filtered or unexported fields
}

AcmeWrapper is the main object which controls tls certificates and their renewals

func New

func New(c Config) (*AcmeWrapper, error)

New generates an AcmeWrapper given a configuration

func (*AcmeWrapper) AcmeDisabled

func (w *AcmeWrapper) AcmeDisabled(set bool) error

AcmeDisabled allows to enable/disable acme-based certificate. Note that it is assumed that this function is only called during server runtime (ie, your server is already listening). its main purpose is to enable live reload of acme configuration. Do NOT set AcmeDisabled in AcmeWrapper.Config, since it will panic.

func (*AcmeWrapper) AddSNI

func (w *AcmeWrapper) AddSNI(domain string, cert *tls.Certificate)

AddSNI adds a domain name and certificate pair to the AcmeWrapper. Whenever a request is for the passed domain, its associated certifcate is returned.

func (*AcmeWrapper) CertNeedsUpdate

func (w *AcmeWrapper) CertNeedsUpdate() bool

CertNeedsUpdate returns whether the current certificate either does not exist, or is <X days from expiration, where X is set up in config

func (*AcmeWrapper) GetCertificate

func (w *AcmeWrapper) GetCertificate() *tls.Certificate

GetCertificate returns the current TLS certificate

func (*AcmeWrapper) GetEmail

func (w *AcmeWrapper) GetEmail() string

GetEmail returns the user email (if any) NOTE: NOT threadsafe

func (*AcmeWrapper) GetPrivateKey

func (w *AcmeWrapper) GetPrivateKey() crypto.PrivateKey

GetPrivateKey returns the private key for the given user. NOTE: NOT threadsafe

func (*AcmeWrapper) GetRegistration

func (w *AcmeWrapper) GetRegistration() *acme.RegistrationResource

GetRegistration returns the registration currently being used NOTE: NOT threadsafe

func (*AcmeWrapper) RemSNI

func (w *AcmeWrapper) RemSNI(domain string)

RemSNI removes a domain name and certificate pair from the AcmeWrapper. It is assumed that they were added using AddSNI.

func (*AcmeWrapper) Renew

func (w *AcmeWrapper) Renew() (err error)

Renew generates a new certificate

func (*AcmeWrapper) SetNewCert

func (w *AcmeWrapper) SetNewCert(certfile, keyfile string) error

SetNewCert loads a new TLS key/cert from the given files. Running it with the same filenames as existing cert will reload them

func (*AcmeWrapper) TLSConfig

func (w *AcmeWrapper) TLSConfig() *tls.Config

TLSConfig returns a TLS configuration that will automatically work with the golang ssl listener. This sets it up so that the server automatically uses a working cert, and updates the cert when necessary.

func (*AcmeWrapper) TLSConfigGetCertificate

func (w *AcmeWrapper) TLSConfigGetCertificate(clientHello *tls.ClientHelloInfo) (*tls.Certificate, error)

TLSConfigGetCertificate is the main function used in the ACME wrapper. This is set in tls.Config to the GetCertificate property. Note that Certificates must be empty for it to be called correctly, so unless you know what you're doing, just use AcmeWrapper.Get()

type Config

type Config struct {
	// The ACME server to query for key/cert. Default (Let's Encrpyt) used if not set
	Server string

	// The domain for which to generate your certificate. Suppose you own mysite.com.
	// The domains to pass in are Domains: []string{"mysite.com","www.mysite.com"}. Don't
	// forget about the www version of your domain.
	Domains []string

	// The file to read/write the private key from/to. If this is not empty, and the file does not exist,
	// then the user is assumed not to be registered, and the file is created. if this is empty, then
	// a new private key is generated and used for all queries. The private key is lost on stopping the program.
	PrivateKeyFile string
	PrivateKeyType acme.KeyType // The private key type. Default is 2048 (RSA)

	// The file to read/write registration info to. The ACME protocol requires remembering some details
	// about a registration. Therefore, the file is saved at the given location.
	// If not given, and PrivateKeyFile is given, then gives an error - if you're saving your private key,
	// you need to save your user registration.
	RegistrationFile string

	Email string `json:"email"` // Optional user email

	// File names at which to read/write the TLS key and certificate. These are optional. If there
	// is no file given, then the keys are kept in memory. NOTE: You need write access to these files,
	// since they are overwritten each time a new certificate is requested.
	// Also, it is HIGHLY recommended that you save the files, since Let's Encrypt has fairly low limits
	// for how often certs for the same site can be requested (5/week at the time of writing).
	TLSCertFile string
	TLSKeyFile  string

	RenewTime  int64 // The time in seconds until expiration of current cert that renew is attempted. If not set, default is 30d
	RetryDelay int64 // The time in seconds to delay between attempts at renewing if renewal fails. (1 day)
	RenewCheck int64 // The time inbetween checks for renewal. Default is 12h

	// The callback to use prompting the user to agree to the terms of service. A special Agree is built in, so
	// you can set TOSCallback: TOSAgree
	TOSCallback TOSCallback

	// If there is no certificate set up at all, we need to generate an inital one
	// to jump-start the server. Therefore, you should input the port that you
	// will use when running listen. If there are no certs, it runs a temporary mini
	// server at that location to generate initial certificates. Once that is done,
	// all further renewals are done through the SNI interface to your own server code.
	// The default here is 443
	Address string

	// This callback is run before each attempt at renewing. If not set, it simply isn't run.
	RenewCallback func()
	// RenewFailedCallback is run if renewing failed.
	RenewFailedCallback func(error)

	// When this is set to True, no ACME-related things happen - it just passes through your
	// key and cert directly.
	AcmeDisabled bool
}

Config is the setup to use for generating your TLS keys. While the only required component is Server, it is recommended that you save at least your TLS cert and key

type LoggerInterface

type LoggerInterface interface {
	Printf(format string, v ...interface{})
}
var Logger LoggerInterface

Allows to use a custom logger for logging purposes

type TOSCallback

type TOSCallback func(agreementURL string) bool

TOSCallback is a callback to run when the TOS have changed, and need to be agreed to. The returned bool is agree/not agree

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL