diff --git a/Justfile b/Justfile index 2f40499..073fedb 100644 --- a/Justfile +++ b/Justfile @@ -16,7 +16,7 @@ build: CGO_ENABLED=1 go build -tags '{{TAGS}}' -ldflags '-s -w {{CGO_FLAGS}}' -v -o build/codeberg-pages-server ./ build-tag VERSION: - CGO_ENABLED=1 go build -tags '{{TAGS}}' '-ldflags '-s -w -X "codeberg.org/codeberg/pages/server/version.Version={{VERSION}}" {{CGO_FLAGS}}' -v -o build/codeberg-pages-server ./ + CGO_ENABLED=1 go build -tags '{{TAGS}}' -ldflags '-s -w -X "codeberg.org/codeberg/pages/server/version.Version={{VERSION}}" {{CGO_FLAGS}}' -v -o build/codeberg-pages-server ./ lint: tool-golangci tool-gofumpt [ $(gofumpt -extra -l . | wc -l) != 0 ] && { echo 'code not formated'; exit 1; }; \ diff --git a/README.md b/README.md index 50cdc3c..6d5a294 100644 --- a/README.md +++ b/README.md @@ -102,6 +102,7 @@ Thank you very much. run `just dev` now this pages should work: - - https://magiclike.localhost.mock.directory:4430/ + - https://cb_pages_tests.localhost.mock.directory:4430/images/827679288a.jpg - https://momar.localhost.mock.directory:4430/ci-testing/ - https://momar.localhost.mock.directory:4430/pag/@master/ + - https://mock-pages.codeberg-test.org:4430/README.md diff --git a/go.mod b/go.mod index 35591ff..2e75c8e 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module codeberg.org/codeberg/pages -go 1.19 +go 1.20 require ( code.gitea.io/sdk/gitea v0.15.1-0.20220729105105-cc14c63cccfa diff --git a/server/certificates/certificates.go b/server/certificates/certificates.go index 2663c67..935139e 100644 --- a/server/certificates/certificates.go +++ b/server/certificates/certificates.go @@ -95,10 +95,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") @@ -110,12 +109,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", @@ -196,54 +194,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 { - log.Error().Err(err).Msgf("could not create tlsCert from key pair: %v", res) + 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:] @@ -256,16 +253,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 @@ -288,7 +285,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 } } @@ -311,30 +308,38 @@ 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) { diff --git a/server/certificates/mock.go b/server/certificates/mock.go index 0e87e6e..7c6ce33 100644 --- a/server/certificates/mock.go +++ b/server/certificates/mock.go @@ -17,10 +17,10 @@ import ( "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 +52,7 @@ func mockCert(domain, msg, mainDomainSuffix string, keyDatabase database.CertDB) key, ) if err != nil { - panic(err) + return nil, err } out := &bytes.Buffer{} @@ -61,7 +61,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{ @@ -80,7 +80,7 @@ func mockCert(domain, msg, mainDomainSuffix string, keyDatabase database.CertDB) tlsCertificate, err := tls.X509KeyPair(res.Certificate, res.PrivateKey) if err != nil { - panic(err) + return nil, err } - return tlsCertificate + return &tlsCertificate, nil } diff --git a/server/database/interface.go b/server/database/interface.go index b551c75..56537a4 100644 --- a/server/database/interface.go +++ b/server/database/interface.go @@ -29,7 +29,6 @@ type Cert struct { PrivateKey []byte `xorm:"'private_key'"` Certificate []byte `xorm:"'certificate'"` IssuerCertificate []byte `xorm:"'issuer_certificate'"` - CSR []byte `xorm:"'csr'"` } func (c Cert) Raw() *certificate.Resource { @@ -40,7 +39,6 @@ func (c Cert) Raw() *certificate.Resource { PrivateKey: c.PrivateKey, Certificate: c.Certificate, IssuerCertificate: c.IssuerCertificate, - CSR: c.CSR, } } @@ -71,6 +69,5 @@ func toCert(name string, c *certificate.Resource) (*Cert, error) { PrivateKey: c.PrivateKey, Certificate: c.Certificate, IssuerCertificate: c.IssuerCertificate, - CSR: c.CSR, }, nil }