simplecert/simplecert.go
2019-07-23 09:27:09 +02:00

192 lines
4.9 KiB
Go

//
// simplecert
//
// Created by Philipp Mieden
// Contact: dreadl0ck@protonmail.ch
// Copyright © 2018 bestbytes. All rights reserved.
//
package simplecert
import (
"encoding/json"
"io"
"io/ioutil"
"log"
"os"
"path/filepath"
"github.com/go-acme/lego/certificate"
)
const (
logFileName = "simplecert.log"
certResourceFileName = "CertResource.json"
certFileName = "cert.pem"
keyFileName = "key.pem"
)
// Init obtains a new LetsEncrypt cert for the specified domains if there is none in cacheDir
// or loads an existing one. Certs will be auto renewed in the configured interval.
// 1. Check if we have a cached certificate, if yes kickoff renewal routine and return
// 2. No Cached Certificate found - make sure the supplied cacheDir exists
// 3. Create a new SSLUser and ACME Client
// 4. Obtain a new certificate
// 5. Save To Disk
// 6. Kickoff Renewal Routine
func Init(cfg *Config) (*CertReloader, error) {
// validate config
err := CheckConfig(cfg)
if err != nil {
return nil, err
}
// config ok.
// update global config
c = cfg
// make sure the cacheDir exists
ensureCacheDirExists(c.CacheDir)
// open logfile handle
logFile, err := os.OpenFile(filepath.Join(c.CacheDir, logFileName), os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0755)
if err != nil {
log.Fatal("[FATAL] simplecert: failed to create logfile: ", err)
}
// configure log pkg to log to stdout and into the logfile
log.SetOutput(io.MultiWriter(os.Stdout, logFile))
if c.Local {
// update the cachedir path
// certs used in local mode are stored in the "local" subfolder
// to avoid overwriting a production certificate
c.CacheDir = filepath.Join(c.CacheDir, "local")
// make sure the cacheDir/local folder exists
ensureCacheDirExists(c.CacheDir)
var (
certFilePath = filepath.Join(c.CacheDir, certFileName)
keyFilePath = filepath.Join(c.CacheDir, keyFileName)
)
// check if a local cert is already cached
if certCached(c.CacheDir) {
// cert cached! Did the domains change?
// If the domains have been modified we need to generate a new certificate
if domainsChanged(certFilePath, keyFilePath) {
log.Println("[INFO] cert cached but domains have changed. generating a new one...")
createLocalCert(certFilePath, keyFilePath)
}
} else {
// nothing there yet. create a new one
createLocalCert(certFilePath, keyFilePath)
}
// create entries in /etc/hosts if necessary
if c.UpdateHosts {
updateHosts()
}
// return a cert reloader for the local cert
return NewCertReloader(certFilePath, keyFilePath, logFile)
}
var (
certFilePath = filepath.Join(c.CacheDir, certFileName)
keyFilePath = filepath.Join(c.CacheDir, keyFileName)
)
// do we have a certificate in cacheDir?
if certCached(c.CacheDir) {
/*
* Cert Found. Load it
*/
if domainsChanged(certFilePath, keyFilePath) {
log.Println("[INFO] domains have changed. Obtaining a new certificate...")
goto obtainNewCert
}
log.Println("[INFO] simplecert: found cert in cacheDir")
// read cert resource from disk
b, err := ioutil.ReadFile(filepath.Join(c.CacheDir, certResourceFileName))
if err != nil {
log.Fatal("[FATAL] simplecert: failed to read CertResource.json from disk: ", err)
}
// unmarshal certificate resource
var cr CR
err = json.Unmarshal(b, &cr)
if err != nil {
log.Fatal("[FATAL] simplecert: failed to unmarshal certificate resource: ", err)
}
cert := getACMECertResource(cr)
// renew cert if necessary
errRenew := renew(cert)
if errRenew != nil {
log.Fatal("[FATAL] failed to renew cached cert on startup: ", errRenew)
}
// kickoff renewal routine
go renewalRoutine(cert)
return NewCertReloader(certFilePath, keyFilePath, logFile)
}
obtainNewCert:
/*
* No Cert Found. Register a new one
*/
u, err := getUser()
if err != nil {
log.Fatal("[FATAL] failed to get ACME user: ", err)
}
// get ACME Client
client, err := createClient(u)
if err != nil {
log.Fatal("[FATAL] failed to create lego.Client: ", err)
}
// bundle CA with certificate to avoid "transport: x509: certificate signed by unknown authority" error
request := certificate.ObtainRequest{
Domains: c.Domains,
Bundle: true,
}
// Obtain a new certificate
// The acme library takes care of completing the challenges to obtain the certificate(s).
// The domains must resolve to this machine or you have to use the DNS challenge.
cert, err := client.Certificate.Obtain(request)
if err != nil {
log.Fatal("[FATAL] simplecert: failed to obtain cert: ", err)
}
log.Println("[INFO] simplecert: client obtained cert for domain: ", cert.Domain)
// Save cert to disk
err = saveCertToDisk(cert, c.CacheDir)
if err != nil {
log.Fatal("[FATAL] simplecert: failed to write cert to disk")
}
log.Println("[INFO] simplecert: wrote new cert to disk!")
// kickoff renewal routine
go renewalRoutine(cert)
return NewCertReloader(certFilePath, keyFilePath, logFile)
}