mirror of
https://codeberg.org/Codeberg/pages-server.git
synced 2025-04-19 03:26:57 +00:00
Add cert store option based on sqlite3, mysql & postgres (#173)
Deprecate **pogreb**! close #169 Reviewed-on: https://codeberg.org/Codeberg/pages-server/pulls/173
This commit is contained in:
parent
7fce7cf68b
commit
7b35a192bf
22 changed files with 1000 additions and 255 deletions
|
@ -1,14 +1,12 @@
|
|||
package certificates
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
"crypto/rand"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"encoding/gob"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
@ -100,10 +98,9 @@ func TLSConfig(mainDomainSuffix string,
|
|||
return tlsCertificate.(*tls.Certificate), nil
|
||||
}
|
||||
|
||||
var tlsCertificate tls.Certificate
|
||||
var tlsCertificate *tls.Certificate
|
||||
var err error
|
||||
var ok bool
|
||||
if tlsCertificate, ok = retrieveCertFromDB(sni, mainDomainSuffix, dnsProvider, acmeUseRateLimits, certDB); !ok {
|
||||
if tlsCertificate, err = retrieveCertFromDB(sni, mainDomainSuffix, dnsProvider, acmeUseRateLimits, certDB); err != nil {
|
||||
// request a new certificate
|
||||
if strings.EqualFold(sni, mainDomainSuffix) {
|
||||
return nil, errors.New("won't request certificate for main domain, something really bad has happened")
|
||||
|
@ -119,12 +116,11 @@ func TLSConfig(mainDomainSuffix string,
|
|||
}
|
||||
}
|
||||
|
||||
if err := keyCache.Set(sni, &tlsCertificate, 15*time.Minute); err != nil {
|
||||
if err := keyCache.Set(sni, tlsCertificate, 15*time.Minute); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &tlsCertificate, nil
|
||||
return tlsCertificate, nil
|
||||
},
|
||||
PreferServerCipherSuites: true,
|
||||
NextProtos: []string{
|
||||
"h2",
|
||||
"http/1.1",
|
||||
|
@ -205,54 +201,53 @@ func (a AcmeHTTPChallengeProvider) CleanUp(domain, token, _ string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func retrieveCertFromDB(sni, mainDomainSuffix, dnsProvider string, acmeUseRateLimits bool, certDB database.CertDB) (tls.Certificate, bool) {
|
||||
func retrieveCertFromDB(sni, mainDomainSuffix, dnsProvider string, acmeUseRateLimits bool, certDB database.CertDB) (*tls.Certificate, error) {
|
||||
// parse certificate from database
|
||||
res, err := certDB.Get(sni)
|
||||
if err != nil {
|
||||
panic(err) // TODO: no panic
|
||||
}
|
||||
if res == nil {
|
||||
return tls.Certificate{}, false
|
||||
return nil, err
|
||||
} else if res == nil {
|
||||
return nil, database.ErrNotFound
|
||||
}
|
||||
|
||||
tlsCertificate, err := tls.X509KeyPair(res.Certificate, res.PrivateKey)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// TODO: document & put into own function
|
||||
if !strings.EqualFold(sni, mainDomainSuffix) {
|
||||
tlsCertificate.Leaf, err = x509.ParseCertificate(tlsCertificate.Certificate[0])
|
||||
if err != nil {
|
||||
panic(err)
|
||||
return nil, fmt.Errorf("error parsin leaf tlsCert: %w", err)
|
||||
}
|
||||
|
||||
// renew certificates 7 days before they expire
|
||||
if tlsCertificate.Leaf.NotAfter.Before(time.Now().Add(7 * 24 * time.Hour)) {
|
||||
// TODO: add ValidUntil to custom res struct
|
||||
// TODO: use ValidTill of custom cert struct
|
||||
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
|
||||
return &tlsCertificate, nil
|
||||
}
|
||||
}
|
||||
// TODO: make a queue ?
|
||||
go (func() {
|
||||
res.CSR = nil // acme client doesn't like CSR to be set
|
||||
tlsCertificate, err = obtainCert(acmeClient, []string{sni}, res, "", dnsProvider, mainDomainSuffix, acmeUseRateLimits, certDB)
|
||||
if err != nil {
|
||||
if _, err := obtainCert(acmeClient, []string{sni}, res, "", dnsProvider, mainDomainSuffix, acmeUseRateLimits, certDB); err != nil {
|
||||
log.Error().Msgf("Couldn't renew certificate for %s: %v", sni, err)
|
||||
}
|
||||
})()
|
||||
}
|
||||
}
|
||||
|
||||
return tlsCertificate, true
|
||||
return &tlsCertificate, nil
|
||||
}
|
||||
|
||||
var obtainLocks = sync.Map{}
|
||||
|
||||
func obtainCert(acmeClient *lego.Client, domains []string, renew *certificate.Resource, user, dnsProvider, mainDomainSuffix string, acmeUseRateLimits bool, keyDatabase database.CertDB) (tls.Certificate, error) {
|
||||
func obtainCert(acmeClient *lego.Client, domains []string, renew *certificate.Resource, user, dnsProvider, mainDomainSuffix string, 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:]
|
||||
|
@ -265,16 +260,16 @@ func obtainCert(acmeClient *lego.Client, domains []string, renew *certificate.Re
|
|||
time.Sleep(100 * time.Millisecond)
|
||||
_, working = obtainLocks.Load(name)
|
||||
}
|
||||
cert, ok := retrieveCertFromDB(name, mainDomainSuffix, dnsProvider, acmeUseRateLimits, keyDatabase)
|
||||
if !ok {
|
||||
return tls.Certificate{}, errors.New("certificate failed in synchronous request")
|
||||
cert, err := retrieveCertFromDB(name, mainDomainSuffix, dnsProvider, acmeUseRateLimits, keyDatabase)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("certificate failed in synchronous request: %w", err)
|
||||
}
|
||||
return cert, nil
|
||||
}
|
||||
defer obtainLocks.Delete(name)
|
||||
|
||||
if acmeClient == nil {
|
||||
return mockCert(domains[0], "ACME client uninitialized. This is a server error, please report!", mainDomainSuffix, keyDatabase), nil
|
||||
return mockCert(domains[0], "ACME client uninitialized. This is a server error, please report!", mainDomainSuffix, keyDatabase)
|
||||
}
|
||||
|
||||
// request actual cert
|
||||
|
@ -297,7 +292,7 @@ func obtainCert(acmeClient *lego.Client, domains []string, renew *certificate.Re
|
|||
if res == nil {
|
||||
if user != "" {
|
||||
if err := checkUserLimit(user); err != nil {
|
||||
return tls.Certificate{}, err
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -320,33 +315,42 @@ func obtainCert(acmeClient *lego.Client, domains []string, renew *certificate.Re
|
|||
if renew != nil && renew.CertURL != "" {
|
||||
tlsCertificate, err := tls.X509KeyPair(renew.Certificate, renew.PrivateKey)
|
||||
if err != nil {
|
||||
return mockCert(domains[0], err.Error(), mainDomainSuffix, keyDatabase), err
|
||||
mockC, err2 := mockCert(domains[0], err.Error(), mainDomainSuffix, keyDatabase)
|
||||
if err2 != nil {
|
||||
return nil, errors.Join(err, err2)
|
||||
}
|
||||
return mockC, err
|
||||
}
|
||||
leaf, err := leaf(&tlsCertificate)
|
||||
if err == nil && 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
|
||||
renew.CSR = []byte(strconv.FormatInt(time.Now().Add(6*time.Hour).Unix(), 10))
|
||||
if err := keyDatabase.Put(name, renew); err != nil {
|
||||
return mockCert(domains[0], err.Error(), mainDomainSuffix, keyDatabase), err
|
||||
mockC, err2 := mockCert(domains[0], err.Error(), mainDomainSuffix, keyDatabase)
|
||||
if err2 != nil {
|
||||
return nil, errors.Join(err, err2)
|
||||
}
|
||||
return mockC, err
|
||||
}
|
||||
return tlsCertificate, nil
|
||||
return &tlsCertificate, nil
|
||||
}
|
||||
}
|
||||
return mockCert(domains[0], err.Error(), mainDomainSuffix, keyDatabase), err
|
||||
return mockCert(domains[0], err.Error(), mainDomainSuffix, keyDatabase)
|
||||
}
|
||||
log.Debug().Msgf("Obtained certificate for %v", domains)
|
||||
|
||||
if err := keyDatabase.Put(name, res); err != nil {
|
||||
return tls.Certificate{}, err
|
||||
return nil, err
|
||||
}
|
||||
tlsCertificate, err := tls.X509KeyPair(res.Certificate, res.PrivateKey)
|
||||
if err != nil {
|
||||
return tls.Certificate{}, err
|
||||
return nil, err
|
||||
}
|
||||
return tlsCertificate, nil
|
||||
return &tlsCertificate, nil
|
||||
}
|
||||
|
||||
func SetupAcmeConfig(acmeAPI, acmeMail, acmeEabHmac, acmeEabKID string, acmeAcceptTerms bool) (*lego.Config, error) {
|
||||
// TODO: make it a config flag
|
||||
const configFile = "acme-account.json"
|
||||
var myAcmeAccount AcmeAccount
|
||||
var myAcmeConfig *lego.Config
|
||||
|
@ -431,8 +435,8 @@ func SetupAcmeConfig(acmeAPI, acmeMail, acmeEabHmac, acmeEabKID string, acmeAcce
|
|||
func SetupCertificates(mainDomainSuffix, 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
|
||||
mainCertBytes, err := certDB.Get(mainDomainSuffix)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cert database is not working")
|
||||
if err != nil && !errors.Is(err, database.ErrNotFound) {
|
||||
return fmt.Errorf("cert database is not working: %w", err)
|
||||
}
|
||||
|
||||
acmeClient, err = lego.NewClient(acmeConfig)
|
||||
|
@ -485,41 +489,35 @@ func SetupCertificates(mainDomainSuffix, dnsProvider string, acmeConfig *lego.Co
|
|||
|
||||
func MaintainCertDB(ctx context.Context, interval time.Duration, mainDomainSuffix, dnsProvider string, acmeUseRateLimits bool, certDB database.CertDB) {
|
||||
for {
|
||||
// clean up expired certs
|
||||
now := time.Now()
|
||||
// delete expired certs that will be invalid until next clean up
|
||||
threshold := time.Now().Add(interval)
|
||||
expiredCertCount := 0
|
||||
keyDatabaseIterator := certDB.Items()
|
||||
key, resBytes, err := keyDatabaseIterator.Next()
|
||||
for err == nil {
|
||||
if !strings.EqualFold(string(key), mainDomainSuffix) {
|
||||
resGob := bytes.NewBuffer(resBytes)
|
||||
resDec := gob.NewDecoder(resGob)
|
||||
res := &certificate.Resource{}
|
||||
err = resDec.Decode(res)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
tlsCertificates, err := certcrypto.ParsePEMBundle(res.Certificate)
|
||||
if err != nil || tlsCertificates[0].NotAfter.Before(now) {
|
||||
err := certDB.Delete(string(key))
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msgf("Deleting expired certificate for %q failed", string(key))
|
||||
} else {
|
||||
expiredCertCount++
|
||||
certs, err := certDB.Items(0, 0)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("could not get certs from list")
|
||||
} else {
|
||||
for _, cert := range certs {
|
||||
if !strings.EqualFold(cert.Domain, strings.TrimPrefix(mainDomainSuffix, ".")) {
|
||||
if time.Unix(cert.ValidTill, 0).Before(threshold) {
|
||||
err := certDB.Delete(cert.Domain)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msgf("Deleting expired certificate for %q failed", cert.Domain)
|
||||
} else {
|
||||
expiredCertCount++
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
key, resBytes, err = keyDatabaseIterator.Next()
|
||||
}
|
||||
log.Debug().Msgf("Removed %d expired certificates from the database", expiredCertCount)
|
||||
log.Debug().Msgf("Removed %d expired certificates from the database", expiredCertCount)
|
||||
|
||||
// compact the database
|
||||
msg, err := certDB.Compact()
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("Compacting key database failed")
|
||||
} else {
|
||||
log.Debug().Msgf("Compacted key database: %s", msg)
|
||||
// compact the database
|
||||
msg, err := certDB.Compact()
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("Compacting key database failed")
|
||||
} else {
|
||||
log.Debug().Msgf("Compacted key database: %s", msg)
|
||||
}
|
||||
}
|
||||
|
||||
// update main cert
|
||||
|
@ -530,9 +528,10 @@ func MaintainCertDB(ctx context.Context, interval time.Duration, mainDomainSuffi
|
|||
log.Error().Msgf("Couldn't renew certificate for main domain %q expected main domain cert to exist, but it's missing - seems like the database is corrupted", mainDomainSuffix)
|
||||
} else {
|
||||
tlsCertificates, err := certcrypto.ParsePEMBundle(res.Certificate)
|
||||
|
||||
// renew main certificate 30 days before it expires
|
||||
if tlsCertificates[0].NotAfter.Before(time.Now().Add(30 * 24 * time.Hour)) {
|
||||
if err != nil {
|
||||
log.Error().Err(fmt.Errorf("could not parse cert for mainDomainSuffix: %w", err))
|
||||
} else if tlsCertificates[0].NotAfter.Before(time.Now().Add(30 * 24 * time.Hour)) {
|
||||
// renew main certificate 30 days before it expires
|
||||
go (func() {
|
||||
_, err = obtainCert(mainDomainAcmeClient, []string{"*" + mainDomainSuffix, mainDomainSuffix[1:]}, res, "", dnsProvider, mainDomainSuffix, acmeUseRateLimits, certDB)
|
||||
if err != nil {
|
||||
|
|
|
@ -13,14 +13,15 @@ import (
|
|||
|
||||
"github.com/go-acme/lego/v4/certcrypto"
|
||||
"github.com/go-acme/lego/v4/certificate"
|
||||
"github.com/rs/zerolog/log"
|
||||
|
||||
"codeberg.org/codeberg/pages/server/database"
|
||||
)
|
||||
|
||||
func mockCert(domain, msg, mainDomainSuffix string, keyDatabase database.CertDB) tls.Certificate {
|
||||
func mockCert(domain, msg, mainDomainSuffix string, keyDatabase database.CertDB) (*tls.Certificate, error) {
|
||||
key, err := certcrypto.GeneratePrivateKey(certcrypto.RSA2048)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
template := x509.Certificate{
|
||||
|
@ -52,7 +53,7 @@ func mockCert(domain, msg, mainDomainSuffix string, keyDatabase database.CertDB)
|
|||
key,
|
||||
)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
out := &bytes.Buffer{}
|
||||
|
@ -61,7 +62,7 @@ func mockCert(domain, msg, mainDomainSuffix string, keyDatabase database.CertDB)
|
|||
Type: "CERTIFICATE",
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
return nil, err
|
||||
}
|
||||
outBytes := out.Bytes()
|
||||
res := &certificate.Resource{
|
||||
|
@ -75,12 +76,12 @@ func mockCert(domain, msg, mainDomainSuffix string, keyDatabase database.CertDB)
|
|||
databaseName = mainDomainSuffix
|
||||
}
|
||||
if err := keyDatabase.Put(databaseName, res); err != nil {
|
||||
panic(err)
|
||||
log.Error().Err(err)
|
||||
}
|
||||
|
||||
tlsCertificate, err := tls.X509KeyPair(res.Certificate, res.PrivateKey)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
return nil, err
|
||||
}
|
||||
return tlsCertificate
|
||||
return &tlsCertificate, nil
|
||||
}
|
||||
|
|
|
@ -10,7 +10,8 @@ import (
|
|||
func TestMockCert(t *testing.T) {
|
||||
db, err := database.NewTmpDB()
|
||||
assert.NoError(t, err)
|
||||
cert := mockCert("example.com", "some error msg", "codeberg.page", db)
|
||||
cert, err := mockCert("example.com", "some error msg", "codeberg.page", db)
|
||||
assert.NoError(t, err)
|
||||
if assert.NotEmpty(t, cert) {
|
||||
assert.NotEmpty(t, cert.Certificate)
|
||||
}
|
||||
|
|
|
@ -1,8 +1,11 @@
|
|||
package database
|
||||
|
||||
import (
|
||||
"github.com/akrylysov/pogreb"
|
||||
"fmt"
|
||||
|
||||
"github.com/go-acme/lego/v4/certcrypto"
|
||||
"github.com/go-acme/lego/v4/certificate"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
type CertDB interface {
|
||||
|
@ -10,6 +13,61 @@ type CertDB interface {
|
|||
Put(name string, cert *certificate.Resource) error
|
||||
Get(name string) (*certificate.Resource, error)
|
||||
Delete(key string) error
|
||||
Items(page, pageSize int) ([]*Cert, error)
|
||||
// Compact deprecated // TODO: remove in next version
|
||||
Compact() (string, error)
|
||||
Items() *pogreb.ItemIterator
|
||||
}
|
||||
|
||||
type Cert struct {
|
||||
Domain string `xorm:"pk NOT NULL UNIQUE 'domain'"`
|
||||
Created int64 `xorm:"created NOT NULL DEFAULT 0 'created'"`
|
||||
Updated int64 `xorm:"updated NOT NULL DEFAULT 0 'updated'"`
|
||||
ValidTill int64 `xorm:" NOT NULL DEFAULT 0 'valid_till'"`
|
||||
// certificate.Resource
|
||||
CertURL string `xorm:"'cert_url'"`
|
||||
CertStableURL string `xorm:"'cert_stable_url'"`
|
||||
PrivateKey []byte `xorm:"'private_key'"`
|
||||
Certificate []byte `xorm:"'certificate'"`
|
||||
IssuerCertificate []byte `xorm:"'issuer_certificate'"`
|
||||
}
|
||||
|
||||
func (c Cert) Raw() *certificate.Resource {
|
||||
return &certificate.Resource{
|
||||
Domain: c.Domain,
|
||||
CertURL: c.CertURL,
|
||||
CertStableURL: c.CertStableURL,
|
||||
PrivateKey: c.PrivateKey,
|
||||
Certificate: c.Certificate,
|
||||
IssuerCertificate: c.IssuerCertificate,
|
||||
}
|
||||
}
|
||||
|
||||
func toCert(name string, c *certificate.Resource) (*Cert, error) {
|
||||
tlsCertificates, err := certcrypto.ParsePEMBundle(c.Certificate)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(tlsCertificates) == 0 || tlsCertificates[0] == nil {
|
||||
err := fmt.Errorf("parsed cert resource has no cert")
|
||||
log.Error().Err(err).Str("domain", c.Domain).Msgf("cert: %v", c)
|
||||
return nil, err
|
||||
}
|
||||
validTill := tlsCertificates[0].NotAfter.Unix()
|
||||
|
||||
// TODO: do we need this or can we just go with domain name for wildcard cert
|
||||
// default *.mock cert is prefixed with '.'
|
||||
if name != c.Domain && name[1:] != c.Domain && name[0] != '.' {
|
||||
return nil, fmt.Errorf("domain key and cert domain not equal")
|
||||
}
|
||||
|
||||
return &Cert{
|
||||
Domain: c.Domain,
|
||||
ValidTill: validTill,
|
||||
|
||||
CertURL: c.CertURL,
|
||||
CertStableURL: c.CertStableURL,
|
||||
PrivateKey: c.PrivateKey,
|
||||
Certificate: c.Certificate,
|
||||
IssuerCertificate: c.IssuerCertificate,
|
||||
}, nil
|
||||
}
|
||||
|
|
|
@ -5,7 +5,6 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/OrlovEvgeny/go-mcache"
|
||||
"github.com/akrylysov/pogreb"
|
||||
"github.com/go-acme/lego/v4/certificate"
|
||||
)
|
||||
|
||||
|
@ -43,8 +42,8 @@ func (p tmpDB) Compact() (string, error) {
|
|||
return "Truncate done", nil
|
||||
}
|
||||
|
||||
func (p tmpDB) Items() *pogreb.ItemIterator {
|
||||
panic("ItemIterator not implemented for tmpDB")
|
||||
func (p tmpDB) Items(page, pageSize int) ([]*Cert, error) {
|
||||
return nil, fmt.Errorf("items not implemented for tmpDB")
|
||||
}
|
||||
|
||||
func NewTmpDB() (CertDB, error) {
|
||||
|
|
|
@ -4,6 +4,7 @@ import (
|
|||
"bytes"
|
||||
"context"
|
||||
"encoding/gob"
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
|
@ -62,8 +63,32 @@ func (p aDB) Compact() (string, error) {
|
|||
return fmt.Sprintf("%+v", result), nil
|
||||
}
|
||||
|
||||
func (p aDB) Items() *pogreb.ItemIterator {
|
||||
return p.intern.Items()
|
||||
func (p aDB) Items(_, _ int) ([]*Cert, error) {
|
||||
items := make([]*Cert, 0, p.intern.Count())
|
||||
iterator := p.intern.Items()
|
||||
for {
|
||||
key, resBytes, err := iterator.Next()
|
||||
if err != nil {
|
||||
if errors.Is(err, pogreb.ErrIterationDone) {
|
||||
break
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
res := &certificate.Resource{}
|
||||
if err := gob.NewDecoder(bytes.NewBuffer(resBytes)).Decode(res); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cert, err := toCert(string(key), res)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
items = append(items, cert)
|
||||
}
|
||||
|
||||
return items, nil
|
||||
}
|
||||
|
||||
var _ CertDB = &aDB{}
|
||||
|
@ -82,7 +107,7 @@ func (p aDB) sync() {
|
|||
}
|
||||
}
|
||||
|
||||
func New(path string) (CertDB, error) {
|
||||
func NewPogreb(path string) (CertDB, error) {
|
||||
if path == "" {
|
||||
return nil, fmt.Errorf("path not set")
|
||||
}
|
121
server/database/xorm.go
Normal file
121
server/database/xorm.go
Normal file
|
@ -0,0 +1,121 @@
|
|||
package database
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
|
||||
"github.com/go-acme/lego/v4/certificate"
|
||||
"xorm.io/xorm"
|
||||
|
||||
// register sql driver
|
||||
_ "github.com/go-sql-driver/mysql"
|
||||
_ "github.com/lib/pq"
|
||||
_ "github.com/mattn/go-sqlite3"
|
||||
)
|
||||
|
||||
var _ CertDB = xDB{}
|
||||
|
||||
var ErrNotFound = errors.New("entry not found")
|
||||
|
||||
type xDB struct {
|
||||
engine *xorm.Engine
|
||||
}
|
||||
|
||||
func NewXormDB(dbType, dbConn string) (CertDB, error) {
|
||||
if !supportedDriver(dbType) {
|
||||
return nil, fmt.Errorf("not supported db type '%s'", dbType)
|
||||
}
|
||||
if dbConn == "" {
|
||||
return nil, fmt.Errorf("no db connection provided")
|
||||
}
|
||||
|
||||
e, err := xorm.NewEngine(dbType, dbConn)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := e.Sync2(new(Cert)); err != nil {
|
||||
return nil, fmt.Errorf("cound not sync db model :%w", err)
|
||||
}
|
||||
|
||||
return &xDB{
|
||||
engine: e,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (x xDB) Close() error {
|
||||
return x.engine.Close()
|
||||
}
|
||||
|
||||
func (x xDB) Put(domain string, cert *certificate.Resource) error {
|
||||
log.Trace().Str("domain", cert.Domain).Msg("inserting cert to db")
|
||||
c, err := toCert(domain, cert)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = x.engine.Insert(c)
|
||||
return err
|
||||
}
|
||||
|
||||
func (x xDB) Get(domain string) (*certificate.Resource, error) {
|
||||
// TODO: do we need this or can we just go with domain name for wildcard cert
|
||||
domain = strings.TrimPrefix(domain, ".")
|
||||
|
||||
cert := new(Cert)
|
||||
log.Trace().Str("domain", domain).Msg("get cert from db")
|
||||
if found, err := x.engine.ID(domain).Get(cert); err != nil {
|
||||
return nil, err
|
||||
} else if !found {
|
||||
return nil, fmt.Errorf("%w: name='%s'", ErrNotFound, domain)
|
||||
}
|
||||
return cert.Raw(), nil
|
||||
}
|
||||
|
||||
func (x xDB) Delete(domain string) error {
|
||||
log.Trace().Str("domain", domain).Msg("delete cert from db")
|
||||
_, err := x.engine.ID(domain).Delete(new(Cert))
|
||||
return err
|
||||
}
|
||||
|
||||
func (x xDB) Compact() (string, error) {
|
||||
// not needed
|
||||
return "", nil
|
||||
}
|
||||
|
||||
// Items return al certs from db, if pageSize is 0 it does not use limit
|
||||
func (x xDB) Items(page, pageSize int) ([]*Cert, error) {
|
||||
// paginated return
|
||||
if pageSize > 0 {
|
||||
certs := make([]*Cert, 0, pageSize)
|
||||
if page >= 0 {
|
||||
page = 1
|
||||
}
|
||||
err := x.engine.Limit(pageSize, (page-1)*pageSize).Find(&certs)
|
||||
return certs, err
|
||||
}
|
||||
|
||||
// return all
|
||||
certs := make([]*Cert, 0, 64)
|
||||
err := x.engine.Find(&certs)
|
||||
return certs, err
|
||||
}
|
||||
|
||||
// Supported database drivers
|
||||
const (
|
||||
DriverSqlite = "sqlite3"
|
||||
DriverMysql = "mysql"
|
||||
DriverPostgres = "postgres"
|
||||
)
|
||||
|
||||
func supportedDriver(driver string) bool {
|
||||
switch driver {
|
||||
case DriverMysql, DriverPostgres, DriverSqlite:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
|
@ -17,17 +17,17 @@ func (o *Options) GetBranchTimestamp(giteaClient *gitea.Client) (bool, error) {
|
|||
// Get default branch
|
||||
defaultBranch, err := giteaClient.GiteaGetRepoDefaultBranch(o.TargetOwner, o.TargetRepo)
|
||||
if err != nil {
|
||||
log.Err(err).Msg("Could't fetch default branch from repository")
|
||||
log.Err(err).Msg("Couldn't fetch default branch from repository")
|
||||
return false, err
|
||||
}
|
||||
log.Debug().Msgf("Succesfully fetched default branch %q from Gitea", defaultBranch)
|
||||
log.Debug().Msgf("Successfully fetched default branch %q from Gitea", defaultBranch)
|
||||
o.TargetBranch = defaultBranch
|
||||
}
|
||||
|
||||
timestamp, err := giteaClient.GiteaGetRepoBranchTimestamp(o.TargetOwner, o.TargetRepo, o.TargetBranch)
|
||||
if err != nil {
|
||||
if !errors.Is(err, gitea.ErrorNotFound) {
|
||||
log.Error().Err(err).Msg("Could not get latest commit's timestamp from branch")
|
||||
log.Error().Err(err).Msg("Could not get latest commit timestamp from branch")
|
||||
}
|
||||
return false, err
|
||||
}
|
||||
|
@ -36,7 +36,7 @@ func (o *Options) GetBranchTimestamp(giteaClient *gitea.Client) (bool, error) {
|
|||
return false, fmt.Errorf("empty response")
|
||||
}
|
||||
|
||||
log.Debug().Msgf("Succesfully fetched latest commit's timestamp from branch: %#v", timestamp)
|
||||
log.Debug().Msgf("Successfully fetched latest commit timestamp from branch: %#v", timestamp)
|
||||
o.BranchTimestamp = timestamp.Timestamp
|
||||
o.TargetBranch = timestamp.Branch
|
||||
return true, nil
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue