2021-12-05 14:21:05 +00:00
package certificates
2021-03-16 23:34:31 +00:00
import (
2021-07-13 08:28:36 +00:00
"bytes"
2021-12-05 17:26:54 +00:00
"context"
2021-07-13 13:45:28 +00:00
"crypto/ecdsa"
"crypto/elliptic"
2021-07-08 23:15:42 +00:00
"crypto/rand"
2021-03-16 23:34:31 +00:00
"crypto/tls"
2021-07-08 23:15:42 +00:00
"crypto/x509"
2021-11-20 18:36:12 +00:00
"encoding/gob"
2021-11-20 14:54:52 +00:00
"encoding/json"
2021-07-13 13:45:28 +00:00
"errors"
2021-12-05 22:20:34 +00:00
"fmt"
2021-11-20 14:54:52 +00:00
"io/ioutil"
2021-07-13 13:45:28 +00:00
"os"
2021-12-01 21:49:48 +00:00
"strconv"
2021-07-08 23:15:42 +00:00
"strings"
2021-11-20 14:54:52 +00:00
"sync"
2021-07-08 23:15:42 +00:00
"time"
2021-07-13 13:45:28 +00:00
"github.com/go-acme/lego/v4/certcrypto"
2021-12-03 02:44:21 +00:00
"github.com/go-acme/lego/v4/certificate"
"github.com/go-acme/lego/v4/challenge"
"github.com/go-acme/lego/v4/challenge/tlsalpn01"
2021-07-13 13:45:28 +00:00
"github.com/go-acme/lego/v4/lego"
2021-12-03 02:44:21 +00:00
"github.com/go-acme/lego/v4/providers/dns"
2021-07-13 13:45:28 +00:00
"github.com/go-acme/lego/v4/registration"
2021-12-05 15:33:56 +00:00
"github.com/reugn/equalizer"
"github.com/rs/zerolog/log"
2021-12-03 02:44:21 +00:00
2021-12-03 03:15:48 +00:00
"codeberg.org/codeberg/pages/server/cache"
2021-12-03 02:44:21 +00:00
"codeberg.org/codeberg/pages/server/database"
2021-12-05 14:21:05 +00:00
dnsutils "codeberg.org/codeberg/pages/server/dns"
"codeberg.org/codeberg/pages/server/upstream"
2021-03-16 23:34:31 +00:00
)
2021-12-03 03:15:48 +00:00
// TLSConfig returns the configuration for generating, serving and cleaning up Let's Encrypt certificates.
2021-12-05 14:02:44 +00:00
func TLSConfig ( mainDomainSuffix [ ] byte ,
2021-12-05 18:02:26 +00:00
giteaRoot , giteaAPIToken , dnsProvider string ,
2021-12-05 14:02:44 +00:00
acmeUseRateLimits bool ,
keyCache , challengeCache , dnsLookupCache , canonicalDomainCache cache . SetGetKey ,
2021-12-05 18:02:26 +00:00
certDB database . CertDB ) * tls . Config {
2021-12-05 13:45:17 +00:00
return & tls . Config {
// check DNS name & get certificate from Let's Encrypt
GetCertificate : func ( info * tls . ClientHelloInfo ) ( * tls . Certificate , error ) {
sni := strings . ToLower ( strings . TrimSpace ( info . ServerName ) )
sniBytes := [ ] byte ( sni )
if len ( sni ) < 1 {
return nil , errors . New ( "missing sni" )
}
2021-07-13 13:45:28 +00:00
2021-12-05 13:45:17 +00:00
if info . SupportedProtos != nil {
for _ , proto := range info . SupportedProtos {
if proto == tlsalpn01 . ACMETLS1Protocol {
2021-12-03 03:32:30 +00:00
challenge , ok := challengeCache . Get ( sni )
2021-12-05 13:45:17 +00:00
if ! ok {
return nil , errors . New ( "no challenge for this domain" )
}
cert , err := tlsalpn01 . ChallengeCert ( sni , challenge . ( string ) )
if err != nil {
return nil , err
}
return cert , nil
2021-07-13 13:45:28 +00:00
}
}
}
2021-12-05 13:45:17 +00:00
targetOwner := ""
if bytes . HasSuffix ( sniBytes , mainDomainSuffix ) || bytes . Equal ( sniBytes , mainDomainSuffix [ 1 : ] ) {
// deliver default certificate for the main domain (*.codeberg.page)
sniBytes = mainDomainSuffix
2021-11-20 14:30:58 +00:00
sni = string ( sniBytes )
} else {
2021-12-05 13:45:17 +00:00
var targetRepo , targetBranch string
2021-12-05 14:21:05 +00:00
targetOwner , targetRepo , targetBranch = dnsutils . GetTargetFromDNS ( sni , string ( mainDomainSuffix ) , dnsLookupCache )
2021-12-05 13:45:17 +00:00
if targetOwner == "" {
// DNS not set up, return main certificate to redirect to the docs
sniBytes = mainDomainSuffix
2021-11-20 14:30:58 +00:00
sni = string ( sniBytes )
2021-12-05 13:45:17 +00:00
} else {
_ , _ = targetRepo , targetBranch
2021-12-05 18:02:26 +00:00
_ , valid := upstream . CheckCanonicalDomain ( targetOwner , targetRepo , targetBranch , sni , string ( mainDomainSuffix ) , giteaRoot , giteaAPIToken , canonicalDomainCache )
2021-12-05 13:45:17 +00:00
if ! valid {
sniBytes = mainDomainSuffix
sni = string ( sniBytes )
}
2021-11-20 14:30:58 +00:00
}
2021-07-13 13:45:28 +00:00
}
2021-12-05 13:45:17 +00:00
if tlsCertificate , ok := keyCache . Get ( sni ) ; ok {
// we can use an existing certificate object
return tlsCertificate . ( * tls . Certificate ) , nil
}
var tlsCertificate tls . Certificate
var err error
var ok bool
2021-12-05 18:02:26 +00:00
if tlsCertificate , ok = retrieveCertFromDB ( sniBytes , mainDomainSuffix , dnsProvider , acmeUseRateLimits , certDB ) ; ! ok {
2021-12-05 13:45:17 +00:00
// request a new certificate
if bytes . Equal ( sniBytes , mainDomainSuffix ) {
return nil , errors . New ( "won't request certificate for main domain, something really bad has happened" )
}
2021-11-20 14:30:58 +00:00
2021-12-05 18:02:26 +00:00
tlsCertificate , err = obtainCert ( acmeClient , [ ] string { sni } , nil , targetOwner , dnsProvider , mainDomainSuffix , acmeUseRateLimits , certDB )
2021-12-05 13:45:17 +00:00
if err != nil {
return nil , err
}
2021-07-13 13:45:28 +00:00
}
2021-11-20 14:30:58 +00:00
2021-12-05 22:20:34 +00:00
if err := keyCache . Set ( sni , & tlsCertificate , 15 * time . Minute ) ; err != nil {
return nil , err
2021-07-13 13:45:28 +00:00
}
2021-12-05 13:45:17 +00:00
return & tlsCertificate , nil
} ,
PreferServerCipherSuites : true ,
NextProtos : [ ] string {
"http/1.1" ,
tlsalpn01 . ACMETLS1Protocol ,
} ,
2021-07-13 13:45:28 +00:00
2021-12-05 13:45:17 +00:00
// generated 2021-07-13, Mozilla Guideline v5.6, Go 1.14.4, intermediate configuration
// https://ssl-config.mozilla.org/#server=go&version=1.14.4&config=intermediate&guideline=5.6
MinVersion : tls . VersionTLS12 ,
CipherSuites : [ ] uint16 {
tls . TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 ,
tls . TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 ,
tls . TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 ,
tls . TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 ,
tls . TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305 ,
tls . TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305 ,
} ,
}
2021-07-13 08:28:36 +00:00
}
2021-12-05 15:24:26 +00:00
func checkUserLimit ( user string ) error {
2021-11-20 14:30:58 +00:00
userLimit , ok := acmeClientCertificateLimitPerUser [ user ]
if ! ok {
// Each Codeberg user can only add 10 new domains per day.
2021-11-25 15:12:28 +00:00
userLimit = equalizer . NewTokenBucket ( 10 , time . Hour * 24 )
2021-11-20 14:30:58 +00:00
acmeClientCertificateLimitPerUser [ user ] = userLimit
2021-07-13 08:28:36 +00:00
}
2021-11-20 14:30:58 +00:00
if ! userLimit . Ask ( ) {
return errors . New ( "rate limit exceeded: 10 certificates per user per 24 hours" )
2021-07-13 08:28:36 +00:00
}
2021-11-20 14:30:58 +00:00
return nil
2021-07-13 08:28:36 +00:00
}
2021-07-13 13:45:28 +00:00
2021-11-20 14:54:52 +00:00
var acmeClient , mainDomainAcmeClient * lego . Client
2021-08-22 15:59:30 +00:00
var acmeClientCertificateLimitPerUser = map [ string ] * equalizer . TokenBucket { }
2021-07-13 13:45:28 +00:00
2021-11-20 18:36:12 +00:00
// rate limit is 300 / 3 hours, we want 200 / 2 hours but to refill more often, so that's 25 new domains every 15 minutes
// TODO: when this is used a lot, we probably have to think of a somewhat better solution?
2021-11-25 15:12:28 +00:00
var acmeClientOrderLimit = equalizer . NewTokenBucket ( 25 , 15 * time . Minute )
2021-11-20 18:36:12 +00:00
2021-12-01 15:23:37 +00:00
// rate limit is 20 / second, we want 5 / second (especially as one cert takes at least two requests)
var acmeClientRequestLimit = equalizer . NewTokenBucket ( 5 , 1 * time . Second )
2021-11-20 18:36:12 +00:00
2021-12-03 03:32:30 +00:00
type AcmeTLSChallengeProvider struct {
challengeCache cache . SetGetKey
}
2021-11-25 15:12:28 +00:00
2021-12-03 03:32:30 +00:00
// make sure AcmeTLSChallengeProvider match Provider interface
2021-07-13 13:45:28 +00:00
var _ challenge . Provider = AcmeTLSChallengeProvider { }
2021-11-25 15:12:28 +00:00
2021-07-13 13:45:28 +00:00
func ( a AcmeTLSChallengeProvider ) Present ( domain , _ , keyAuth string ) error {
2021-12-03 03:32:30 +00:00
return a . challengeCache . Set ( domain , keyAuth , 1 * time . Hour )
2021-07-13 13:45:28 +00:00
}
func ( a AcmeTLSChallengeProvider ) CleanUp ( domain , _ , _ string ) error {
2021-12-03 03:32:30 +00:00
a . challengeCache . Remove ( domain )
2021-07-13 13:45:28 +00:00
return nil
}
2021-11-25 15:12:28 +00:00
2021-12-03 03:32:30 +00:00
type AcmeHTTPChallengeProvider struct {
challengeCache cache . SetGetKey
}
2021-11-25 15:12:28 +00:00
2021-12-03 03:32:30 +00:00
// make sure AcmeHTTPChallengeProvider match Provider interface
2021-11-20 20:10:46 +00:00
var _ challenge . Provider = AcmeHTTPChallengeProvider { }
2021-11-25 15:12:28 +00:00
2021-11-20 20:10:46 +00:00
func ( a AcmeHTTPChallengeProvider ) Present ( domain , token , keyAuth string ) error {
2021-12-03 03:32:30 +00:00
return a . challengeCache . Set ( domain + "/" + token , keyAuth , 1 * time . Hour )
2021-11-20 20:10:46 +00:00
}
func ( a AcmeHTTPChallengeProvider ) CleanUp ( domain , token , _ string ) error {
2021-12-03 03:32:30 +00:00
a . challengeCache . Remove ( domain + "/" + token )
2021-11-20 20:10:46 +00:00
return nil
}
2021-07-13 13:45:28 +00:00
2021-12-05 18:02:26 +00:00
func retrieveCertFromDB ( sni , mainDomainSuffix [ ] byte , dnsProvider string , acmeUseRateLimits bool , certDB database . CertDB ) ( tls . Certificate , bool ) {
2021-11-20 14:54:52 +00:00
// parse certificate from database
2021-12-05 18:02:26 +00:00
res , err := certDB . Get ( sni )
2021-12-05 18:00:57 +00:00
if err != nil {
panic ( err ) // TODO: no panic
}
if res == nil {
2021-12-01 21:49:48 +00:00
return tls . Certificate { } , false
2021-11-20 14:54:52 +00:00
}
2021-11-20 18:36:12 +00:00
tlsCertificate , err := tls . X509KeyPair ( res . Certificate , res . PrivateKey )
2021-11-20 14:54:52 +00:00
if err != nil {
panic ( err )
}
2021-11-20 18:36:12 +00:00
2021-12-02 18:12:45 +00:00
// TODO: document & put into own function
2021-12-05 13:45:17 +00:00
if ! bytes . Equal ( sni , mainDomainSuffix ) {
2021-11-20 18:36:12 +00:00
tlsCertificate . Leaf , err = x509 . ParseCertificate ( tlsCertificate . Certificate [ 0 ] )
if err != nil {
panic ( err )
}
// renew certificates 7 days before they expire
if ! tlsCertificate . Leaf . NotAfter . After ( time . Now ( ) . Add ( - 7 * 24 * time . Hour ) ) {
2021-12-02 18:12:45 +00:00
// TODO: add ValidUntil to custom res struct
2021-12-01 21:49:48 +00:00
if res . CSR != nil && len ( res . CSR ) > 0 {
// CSR stores the time when the renewal shall be tried again
nextTryUnix , err := strconv . ParseInt ( string ( res . CSR ) , 10 , 64 )
if err == nil && time . Now ( ) . Before ( time . Unix ( nextTryUnix , 0 ) ) {
return tlsCertificate , true
}
}
2021-11-20 18:36:12 +00:00
go ( func ( ) {
2021-12-01 21:49:48 +00:00
res . CSR = nil // acme client doesn't like CSR to be set
2021-12-05 18:02:26 +00:00
tlsCertificate , err = obtainCert ( acmeClient , [ ] string { string ( sni ) } , res , "" , dnsProvider , mainDomainSuffix , acmeUseRateLimits , certDB )
2021-11-20 18:36:12 +00:00
if err != nil {
log . Printf ( "Couldn't renew certificate for %s: %s" , sni , err )
}
} ) ( )
}
}
2021-11-20 14:54:52 +00:00
return tlsCertificate , true
}
var obtainLocks = sync . Map { }
2021-11-25 15:12:28 +00:00
2021-12-05 16:42:53 +00:00
func obtainCert ( acmeClient * lego . Client , domains [ ] string , renew * certificate . Resource , user , dnsProvider string , mainDomainSuffix [ ] byte , acmeUseRateLimits bool , keyDatabase database . CertDB ) ( tls . Certificate , error ) {
2021-11-20 14:54:52 +00:00
name := strings . TrimPrefix ( domains [ 0 ] , "*" )
2021-12-03 02:34:50 +00:00
if dnsProvider == "" && len ( domains [ 0 ] ) > 0 && domains [ 0 ] [ 0 ] == '*' {
2021-11-20 14:30:58 +00:00
domains = domains [ 1 : ]
2021-08-22 15:59:30 +00:00
}
2021-07-13 13:45:28 +00:00
2021-11-20 14:54:52 +00:00
// lock to avoid simultaneous requests
_ , working := obtainLocks . LoadOrStore ( name , struct { } { } )
if working {
for working {
time . Sleep ( 100 * time . Millisecond )
_ , working = obtainLocks . Load ( name )
}
2021-12-03 03:15:48 +00:00
cert , ok := retrieveCertFromDB ( [ ] byte ( name ) , mainDomainSuffix , dnsProvider , acmeUseRateLimits , keyDatabase )
2021-11-20 14:54:52 +00:00
if ! ok {
return tls . Certificate { } , errors . New ( "certificate failed in synchronous request" )
}
return cert , nil
}
defer obtainLocks . Delete ( name )
2021-12-01 15:23:37 +00:00
if acmeClient == nil {
2021-12-03 03:15:48 +00:00
return mockCert ( domains [ 0 ] , "ACME client uninitialized. This is a server error, please report!" , string ( mainDomainSuffix ) , keyDatabase ) , nil
2021-12-01 15:23:37 +00:00
}
2021-11-20 18:36:12 +00:00
// request actual cert
var res * certificate . Resource
var err error
2021-12-01 15:23:37 +00:00
if renew != nil && renew . CertURL != "" {
2021-12-03 02:34:50 +00:00
if acmeUseRateLimits {
2021-11-20 20:12:28 +00:00
acmeClientRequestLimit . Take ( )
}
2021-11-20 18:36:12 +00:00
log . Printf ( "Renewing certificate for %v" , domains )
res , err = acmeClient . Certificate . Renew ( * renew , true , false , "" )
2021-12-01 15:23:37 +00:00
if err != nil {
log . Printf ( "Couldn't renew certificate for %v, trying to request a new one: %s" , domains , err )
res = nil
}
}
if res == nil {
2021-11-20 20:39:40 +00:00
if user != "" {
2021-12-05 15:24:26 +00:00
if err := checkUserLimit ( user ) ; err != nil {
2021-11-20 20:39:40 +00:00
return tls . Certificate { } , err
}
}
2021-12-03 02:34:50 +00:00
if acmeUseRateLimits {
2021-11-20 20:12:28 +00:00
acmeClientOrderLimit . Take ( )
acmeClientRequestLimit . Take ( )
}
2021-11-20 18:36:12 +00:00
log . Printf ( "Requesting new certificate for %v" , domains )
res , err = acmeClient . Certificate . Obtain ( certificate . ObtainRequest {
Domains : domains ,
Bundle : true ,
MustStaple : false ,
} )
}
2021-11-20 14:30:58 +00:00
if err != nil {
log . Printf ( "Couldn't obtain certificate for %v: %s" , domains , err )
2021-12-01 21:49:48 +00:00
if renew != nil && renew . CertURL != "" {
tlsCertificate , err := tls . X509KeyPair ( renew . Certificate , renew . PrivateKey )
if err == nil && tlsCertificate . Leaf . NotAfter . After ( time . Now ( ) ) {
// avoid sending a mock cert instead of a still valid cert, instead abuse CSR field to store time to try again at
2021-12-01 21:59:52 +00:00
renew . CSR = [ ] byte ( strconv . FormatInt ( time . Now ( ) . Add ( 6 * time . Hour ) . Unix ( ) , 10 ) )
2021-12-05 18:00:57 +00:00
if err := keyDatabase . Put ( name , renew ) ; err != nil {
return mockCert ( domains [ 0 ] , err . Error ( ) , string ( mainDomainSuffix ) , keyDatabase ) , err
}
2021-12-01 21:49:48 +00:00
return tlsCertificate , nil
}
}
2021-12-03 03:15:48 +00:00
return mockCert ( domains [ 0 ] , err . Error ( ) , string ( mainDomainSuffix ) , keyDatabase ) , err
2021-08-22 15:59:30 +00:00
}
2021-11-20 14:30:58 +00:00
log . Printf ( "Obtained certificate for %v" , domains )
2021-08-22 15:59:30 +00:00
2021-12-05 18:00:57 +00:00
if err := keyDatabase . Put ( name , res ) ; err != nil {
return tls . Certificate { } , err
}
2021-11-20 14:30:58 +00:00
tlsCertificate , err := tls . X509KeyPair ( res . Certificate , res . PrivateKey )
2021-07-13 13:45:28 +00:00
if err != nil {
2021-11-20 14:54:52 +00:00
return tls . Certificate { } , err
2021-07-13 13:45:28 +00:00
}
2021-11-20 14:30:58 +00:00
return tlsCertificate , nil
2021-08-22 15:59:30 +00:00
}
2021-12-05 15:33:56 +00:00
func SetupAcmeConfig ( acmeAPI , acmeMail , acmeEabHmac , acmeEabKID string , acmeAcceptTerms bool ) ( * lego . Config , error ) {
const configFile = "acme-account.json"
var myAcmeAccount AcmeAccount
var myAcmeConfig * lego . Config
2021-12-01 15:23:37 +00:00
2021-12-05 15:33:56 +00:00
if account , err := ioutil . ReadFile ( configFile ) ; err == nil {
2021-12-05 22:20:34 +00:00
if err := json . Unmarshal ( account , & myAcmeAccount ) ; err != nil {
return nil , err
2021-11-20 14:54:52 +00:00
}
myAcmeAccount . Key , err = certcrypto . ParsePEMPrivateKey ( [ ] byte ( myAcmeAccount . KeyPEM ) )
if err != nil {
2021-12-05 22:20:34 +00:00
return nil , err
2021-11-20 14:54:52 +00:00
}
myAcmeConfig = lego . NewConfig ( & myAcmeAccount )
2021-12-03 02:05:38 +00:00
myAcmeConfig . CADirURL = acmeAPI
2021-11-20 14:54:52 +00:00
myAcmeConfig . Certificate . KeyType = certcrypto . RSA2048
2021-12-05 15:33:56 +00:00
// Validate Config
2021-12-01 15:23:37 +00:00
_ , err := lego . NewClient ( myAcmeConfig )
if err != nil {
2021-12-05 15:33:56 +00:00
// TODO: should we fail hard instead?
2021-12-01 15:23:37 +00:00
log . Printf ( "[ERROR] Can't create ACME client, continuing with mock certs only: %s" , err )
}
2021-12-05 15:33:56 +00:00
return myAcmeConfig , nil
} else if ! os . IsNotExist ( err ) {
return nil , err
}
privateKey , err := ecdsa . GenerateKey ( elliptic . P256 ( ) , rand . Reader )
if err != nil {
2021-12-05 22:20:34 +00:00
return nil , err
2021-12-05 15:33:56 +00:00
}
myAcmeAccount = AcmeAccount {
Email : acmeMail ,
Key : privateKey ,
KeyPEM : string ( certcrypto . PEMEncode ( privateKey ) ) ,
}
myAcmeConfig = lego . NewConfig ( & myAcmeAccount )
myAcmeConfig . CADirURL = acmeAPI
myAcmeConfig . Certificate . KeyType = certcrypto . RSA2048
tempClient , err := lego . NewClient ( myAcmeConfig )
if err != nil {
log . Printf ( "[ERROR] Can't create ACME client, continuing with mock certs only: %s" , err )
} else {
// accept terms & log in to EAB
if acmeEabKID == "" || acmeEabHmac == "" {
reg , err := tempClient . Registration . Register ( registration . RegisterOptions { TermsOfServiceAgreed : acmeAcceptTerms } )
if err != nil {
log . Printf ( "[ERROR] Can't register ACME account, continuing with mock certs only: %s" , err )
} else {
myAcmeAccount . Registration = reg
}
2021-11-20 14:54:52 +00:00
} else {
2021-12-05 15:33:56 +00:00
reg , err := tempClient . Registration . RegisterWithExternalAccountBinding ( registration . RegisterEABOptions {
TermsOfServiceAgreed : acmeAcceptTerms ,
Kid : acmeEabKID ,
HmacEncoded : acmeEabHmac ,
} )
if err != nil {
log . Printf ( "[ERROR] Can't register ACME account, continuing with mock certs only: %s" , err )
2021-12-01 15:23:37 +00:00
} else {
2021-12-05 15:33:56 +00:00
myAcmeAccount . Registration = reg
2021-11-20 14:54:52 +00:00
}
2021-12-05 15:33:56 +00:00
}
2021-11-20 14:54:52 +00:00
2021-12-05 15:33:56 +00:00
if myAcmeAccount . Registration != nil {
2021-12-05 22:20:34 +00:00
acmeAccountJSON , err := json . Marshal ( myAcmeAccount )
2021-12-05 15:33:56 +00:00
if err != nil {
log . Printf ( "[FAIL] Error during json.Marshal(myAcmeAccount), waiting for manual restart to avoid rate limits: %s" , err )
select { }
}
2021-12-05 22:20:34 +00:00
err = ioutil . WriteFile ( configFile , acmeAccountJSON , 0600 )
2021-12-05 15:33:56 +00:00
if err != nil {
log . Printf ( "[FAIL] Error during ioutil.WriteFile(\"acme-account.json\"), waiting for manual restart to avoid rate limits: %s" , err )
select { }
2021-12-01 15:23:37 +00:00
}
2021-11-20 14:54:52 +00:00
}
2021-12-05 15:33:56 +00:00
}
return myAcmeConfig , nil
}
2021-12-05 22:20:34 +00:00
func SetupCertificates ( mainDomainSuffix [ ] byte , dnsProvider string , acmeConfig * lego . Config , acmeUseRateLimits , enableHTTPServer bool , challengeCache cache . SetGetKey , certDB database . CertDB ) error {
// getting main cert before ACME account so that we can fail here without hitting rate limits
2021-12-05 18:02:26 +00:00
mainCertBytes , err := certDB . Get ( mainDomainSuffix )
2021-12-05 15:33:56 +00:00
if err != nil {
2021-12-05 22:20:34 +00:00
return fmt . Errorf ( "cert database is not working" )
2021-11-20 14:54:52 +00:00
}
2021-12-05 15:33:56 +00:00
acmeClient , err = lego . NewClient ( acmeConfig )
2021-12-01 15:23:37 +00:00
if err != nil {
log . Printf ( "[ERROR] Can't create ACME client, continuing with mock certs only: %s" , err )
} else {
2021-12-03 03:32:30 +00:00
err = acmeClient . Challenge . SetTLSALPN01Provider ( AcmeTLSChallengeProvider { challengeCache } )
2021-11-20 20:10:46 +00:00
if err != nil {
2021-12-01 15:23:37 +00:00
log . Printf ( "[ERROR] Can't create TLS-ALPN-01 provider: %s" , err )
2021-11-20 20:10:46 +00:00
}
2021-12-03 02:34:50 +00:00
if enableHTTPServer {
2021-12-03 03:32:30 +00:00
err = acmeClient . Challenge . SetHTTP01Provider ( AcmeHTTPChallengeProvider { challengeCache } )
2021-12-01 15:23:37 +00:00
if err != nil {
log . Printf ( "[ERROR] Can't create HTTP-01 provider: %s" , err )
}
2021-11-20 20:10:46 +00:00
}
2021-12-01 15:23:37 +00:00
}
2021-12-05 15:33:56 +00:00
mainDomainAcmeClient , err = lego . NewClient ( acmeConfig )
2021-12-01 15:23:37 +00:00
if err != nil {
log . Printf ( "[ERROR] Can't create ACME client, continuing with mock certs only: %s" , err )
} else {
2021-12-03 02:34:50 +00:00
if dnsProvider == "" {
2021-11-20 14:54:52 +00:00
// using mock server, don't use wildcard certs
2021-12-03 03:32:30 +00:00
err := mainDomainAcmeClient . Challenge . SetTLSALPN01Provider ( AcmeTLSChallengeProvider { challengeCache } )
2021-12-01 15:23:37 +00:00
if err != nil {
log . Printf ( "[ERROR] Can't create TLS-ALPN-01 provider: %s" , err )
}
} else {
2021-12-03 02:34:50 +00:00
provider , err := dns . NewDNSChallengeProviderByName ( dnsProvider )
2021-12-01 15:23:37 +00:00
if err != nil {
log . Printf ( "[ERROR] Can't create DNS Challenge provider: %s" , err )
}
err = mainDomainAcmeClient . Challenge . SetDNS01Provider ( provider )
if err != nil {
log . Printf ( "[ERROR] Can't create DNS-01 provider: %s" , err )
}
2021-11-20 14:54:52 +00:00
}
2021-12-01 15:23:37 +00:00
}
2021-11-20 14:54:52 +00:00
2021-12-01 15:23:37 +00:00
if mainCertBytes == nil {
2021-12-05 18:02:26 +00:00
_ , err = obtainCert ( mainDomainAcmeClient , [ ] string { "*" + string ( mainDomainSuffix ) , string ( mainDomainSuffix [ 1 : ] ) } , nil , "" , dnsProvider , mainDomainSuffix , acmeUseRateLimits , certDB )
2021-11-20 18:36:12 +00:00
if err != nil {
2021-12-01 15:23:37 +00:00
log . Printf ( "[ERROR] Couldn't renew main domain certificate, continuing with mock certs only: %s" , err )
2021-11-20 18:36:12 +00:00
}
}
2021-12-05 22:20:34 +00:00
return nil
2021-12-05 16:44:10 +00:00
}
2021-11-20 18:36:12 +00:00
2021-12-05 18:00:57 +00:00
func MaintainCertDB ( ctx context . Context , interval time . Duration , mainDomainSuffix [ ] byte , dnsProvider string , acmeUseRateLimits bool , certDB database . CertDB ) {
2021-12-05 16:44:10 +00:00
for {
// clean up expired certs
now := time . Now ( )
expiredCertCount := 0
2021-12-05 18:00:57 +00:00
keyDatabaseIterator := certDB . Items ( )
2021-12-05 16:44:10 +00:00
key , resBytes , err := keyDatabaseIterator . Next ( )
for err == nil {
if ! bytes . Equal ( key , mainDomainSuffix ) {
resGob := bytes . NewBuffer ( resBytes )
resDec := gob . NewDecoder ( resGob )
res := & certificate . Resource { }
err = resDec . Decode ( res )
if err != nil {
panic ( err )
}
2021-11-20 18:36:12 +00:00
2021-12-05 16:44:10 +00:00
tlsCertificates , err := certcrypto . ParsePEMBundle ( res . Certificate )
if err != nil || ! tlsCertificates [ 0 ] . NotAfter . After ( now ) {
2021-12-05 18:00:57 +00:00
err := certDB . Delete ( key )
2021-12-05 16:44:10 +00:00
if err != nil {
log . Printf ( "[ERROR] Deleting expired certificate for %s failed: %s" , string ( key ) , err )
} else {
expiredCertCount ++
2021-11-20 14:30:58 +00:00
}
}
}
2021-12-05 16:44:10 +00:00
key , resBytes , err = keyDatabaseIterator . Next ( )
}
log . Printf ( "[INFO] Removed %d expired certificates from the database" , expiredCertCount )
2021-11-20 14:30:58 +00:00
2021-12-05 16:44:10 +00:00
// compact the database
2021-12-05 18:00:57 +00:00
result , err := certDB . Compact ( )
2021-12-05 16:44:10 +00:00
if err != nil {
log . Printf ( "[ERROR] Compacting key database failed: %s" , err )
} else {
log . Printf ( "[INFO] Compacted key database (%+v)" , result )
}
2021-11-20 14:30:58 +00:00
2021-12-05 16:44:10 +00:00
// update main cert
2021-12-05 18:00:57 +00:00
res , err := certDB . Get ( mainDomainSuffix )
if err != nil {
log . Err ( err ) . Msgf ( "could not get cert for domain '%s'" , mainDomainSuffix )
} else if res == nil {
log . Error ( ) . Msgf ( "Couldn't renew certificate for main domain: %s" , "expected main domain cert to exist, but it's missing - seems like the database is corrupted" )
2021-12-05 16:44:10 +00:00
} else {
tlsCertificates , err := certcrypto . ParsePEMBundle ( res . Certificate )
2021-11-20 18:36:12 +00:00
2021-12-05 16:44:10 +00:00
// renew main certificate 30 days before it expires
if ! tlsCertificates [ 0 ] . NotAfter . After ( time . Now ( ) . Add ( - 30 * 24 * time . Hour ) ) {
go ( func ( ) {
2021-12-05 18:00:57 +00:00
_ , err = obtainCert ( mainDomainAcmeClient , [ ] string { "*" + string ( mainDomainSuffix ) , string ( mainDomainSuffix [ 1 : ] ) } , res , "" , dnsProvider , mainDomainSuffix , acmeUseRateLimits , certDB )
2021-12-05 16:44:10 +00:00
if err != nil {
log . Printf ( "[ERROR] Couldn't renew certificate for main domain: %s" , err )
}
} ) ( )
2021-11-20 14:30:58 +00:00
}
2021-07-13 13:45:28 +00:00
}
2021-12-05 16:44:10 +00:00
2021-12-05 17:26:54 +00:00
select {
case <- ctx . Done ( ) :
return
case <- time . After ( interval ) :
}
2021-12-05 16:44:10 +00:00
}
2021-07-13 13:45:28 +00:00
}