mirror of
https://github.com/gosticks/simplecert.git
synced 2025-10-16 11:55:35 +00:00
192 lines
4.9 KiB
Go
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)
|
|
}
|