From de439f9becccbb3f9cd5d32ba14a50b19294da8c Mon Sep 17 00:00:00 2001 From: 6543 <6543@obermui.de> Date: Sun, 5 Dec 2021 17:42:53 +0100 Subject: [PATCH] wrap cert db and make sync gracefull --- cmd/certs.go | 2 +- cmd/main.go | 2 +- server/certificates/certificates.go | 8 +-- server/certificates/mock.go | 2 +- server/database/helpers.go | 4 +- server/database/interface.go | 4 +- server/database/setup.go | 87 ++++++++++++++++++++++++++++- 7 files changed, 95 insertions(+), 14 deletions(-) diff --git a/cmd/certs.go b/cmd/certs.go index 89521da..6603e55 100644 --- a/cmd/certs.go +++ b/cmd/certs.go @@ -35,7 +35,7 @@ func certs(ctx *cli.Context) error { panic(err) } } - if err := keyDatabase.Sync(); err != nil { + if err := keyDatabase.Close(); err != nil { panic(err) } os.Exit(0) diff --git a/cmd/main.go b/cmd/main.go index 222c295..7eb3fac 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -94,7 +94,7 @@ func Serve(ctx *cli.Context) error { if err != nil { return fmt.Errorf("could not create database: %v", err) } - defer keyDatabase.Sync() //nolint:errcheck // database has no close ... sync behave like it + defer keyDatabase.Close() //nolint:errcheck // database has no close ... sync behave like it listener = tls.NewListener(listener, certificates.TLSConfig(mainDomainSuffix, giteaRoot, giteaAPIToken, dnsProvider, diff --git a/server/certificates/certificates.go b/server/certificates/certificates.go index b20f623..d2f7c76 100644 --- a/server/certificates/certificates.go +++ b/server/certificates/certificates.go @@ -38,7 +38,7 @@ func TLSConfig(mainDomainSuffix []byte, giteaRoot, giteaApiToken, dnsProvider string, acmeUseRateLimits bool, keyCache, challengeCache, dnsLookupCache, canonicalDomainCache cache.SetGetKey, - keyDatabase database.KeyDB) *tls.Config { + keyDatabase database.CertDB) *tls.Config { return &tls.Config{ // check DNS name & get certificate from Let's Encrypt GetCertificate: func(info *tls.ClientHelloInfo) (*tls.Certificate, error) { @@ -185,7 +185,7 @@ func (a AcmeHTTPChallengeProvider) CleanUp(domain, token, _ string) error { return nil } -func retrieveCertFromDB(sni, mainDomainSuffix []byte, dnsProvider string, acmeUseRateLimits bool, keyDatabase database.KeyDB) (tls.Certificate, bool) { +func retrieveCertFromDB(sni, mainDomainSuffix []byte, dnsProvider string, acmeUseRateLimits bool, keyDatabase database.CertDB) (tls.Certificate, bool) { // parse certificate from database res := &certificate.Resource{} if !database.PogrebGet(keyDatabase, sni, res) { @@ -229,7 +229,7 @@ func retrieveCertFromDB(sni, mainDomainSuffix []byte, dnsProvider string, acmeUs var obtainLocks = sync.Map{} -func obtainCert(acmeClient *lego.Client, domains []string, renew *certificate.Resource, user, dnsProvider string, mainDomainSuffix []byte, acmeUseRateLimits bool, keyDatabase database.KeyDB) (tls.Certificate, error) { +func obtainCert(acmeClient *lego.Client, domains []string, renew *certificate.Resource, user, dnsProvider string, mainDomainSuffix []byte, acmeUseRateLimits bool, keyDatabase database.CertDB) (tls.Certificate, error) { name := strings.TrimPrefix(domains[0], "*") if dnsProvider == "" && len(domains[0]) > 0 && domains[0][0] == '*' { domains = domains[1:] @@ -392,7 +392,7 @@ func SetupAcmeConfig(acmeAPI, acmeMail, acmeEabHmac, acmeEabKID string, acmeAcce return myAcmeConfig, nil } -func SetupCertificates(mainDomainSuffix []byte, dnsProvider string, acmeConfig *lego.Config, acmeUseRateLimits, enableHTTPServer bool, challengeCache cache.SetGetKey, keyDatabase database.KeyDB) { +func SetupCertificates(mainDomainSuffix []byte, dnsProvider string, acmeConfig *lego.Config, acmeUseRateLimits, enableHTTPServer bool, challengeCache cache.SetGetKey, keyDatabase database.CertDB) { // getting main cert before ACME account so that we can panic here on database failure without hitting rate limits mainCertBytes, err := keyDatabase.Get(mainDomainSuffix) if err != nil { diff --git a/server/certificates/mock.go b/server/certificates/mock.go index 19adb92..22d5470 100644 --- a/server/certificates/mock.go +++ b/server/certificates/mock.go @@ -17,7 +17,7 @@ import ( "codeberg.org/codeberg/pages/server/database" ) -func mockCert(domain, msg, mainDomainSuffix string, keyDatabase database.KeyDB) tls.Certificate { +func mockCert(domain, msg, mainDomainSuffix string, keyDatabase database.CertDB) tls.Certificate { key, err := certcrypto.GeneratePrivateKey(certcrypto.RSA2048) if err != nil { panic(err) diff --git a/server/database/helpers.go b/server/database/helpers.go index 98ea3fa..ea3e899 100644 --- a/server/database/helpers.go +++ b/server/database/helpers.go @@ -5,7 +5,7 @@ import ( "encoding/gob" ) -func PogrebPut(db KeyDB, name []byte, obj interface{}) { +func PogrebPut(db CertDB, name []byte, obj interface{}) { var resGob bytes.Buffer resEnc := gob.NewEncoder(&resGob) err := resEnc.Encode(obj) @@ -18,7 +18,7 @@ func PogrebPut(db KeyDB, name []byte, obj interface{}) { } } -func PogrebGet(db KeyDB, name []byte, obj interface{}) bool { +func PogrebGet(db CertDB, name []byte, obj interface{}) bool { resBytes, err := db.Get(name) if err != nil { panic(err) diff --git a/server/database/interface.go b/server/database/interface.go index 2b582ae..80d74d3 100644 --- a/server/database/interface.go +++ b/server/database/interface.go @@ -2,8 +2,8 @@ package database import "github.com/akrylysov/pogreb" -type KeyDB interface { - Sync() error +type CertDB interface { + Close() error Put(key []byte, value []byte) error Get(key []byte) ([]byte, error) Delete(key []byte) error diff --git a/server/database/setup.go b/server/database/setup.go index c16ff36..f7eeafc 100644 --- a/server/database/setup.go +++ b/server/database/setup.go @@ -1,19 +1,100 @@ package database import ( + "context" "fmt" + "time" + + "github.com/rs/zerolog/log" + "github.com/akrylysov/pogreb" "github.com/akrylysov/pogreb/fs" - "time" ) -func New(path string) (KeyDB, error) { +type aDB struct { + ctx context.Context + cancel context.CancelFunc + intern *pogreb.DB + syncInterval time.Duration +} + +func (p aDB) Close() error { + p.cancel() + return p.intern.Sync() +} + +func (p aDB) Put(key []byte, value []byte) error { + return p.intern.Put(key, value) +} + +func (p aDB) Get(key []byte) ([]byte, error) { + return p.intern.Get(key) +} + +func (p aDB) Delete(key []byte) error { + return p.intern.Delete(key) +} + +func (p aDB) Compact() (pogreb.CompactionResult, error) { + return p.intern.Compact() +} + +func (p aDB) Items() *pogreb.ItemIterator { + return p.intern.Items() +} + +var _ CertDB = &aDB{} + +func (p aDB) sync() { + for { + err := p.intern.Sync() + if err != nil { + log.Err(err).Msg("Syncing cert database failed") + } + select { + case <-p.ctx.Done(): + return + case <-time.After(p.syncInterval): + } + } +} + +func (p aDB) compact() { + for { + err := p.intern.Sync() + if err != nil { + log.Err(err).Msg("Syncing cert database failed") + } + select { + case <-p.ctx.Done(): + return + case <-time.After(p.syncInterval): + } + } +} + +func New(path string) (CertDB, error) { if path == "" { return nil, fmt.Errorf("path not set") } - return pogreb.Open(path, &pogreb.Options{ + db, err := pogreb.Open(path, &pogreb.Options{ BackgroundSyncInterval: 30 * time.Second, BackgroundCompactionInterval: 6 * time.Hour, FileSystem: fs.OSMMap, }) + if err != nil { + return nil, err + } + + ctx, cancel := context.WithCancel(context.Background()) + result := &aDB{ + ctx: ctx, + cancel: cancel, + intern: db, + syncInterval: 5 * time.Minute, + } + + go result.sync() + + return result, nil }