mirror of
https://codeberg.org/Codeberg/pages-server.git
synced 2025-04-24 22:06:57 +00:00
less panic and more fixing
This commit is contained in:
parent
d5b9f255f5
commit
0d855ac441
6 changed files with 46 additions and 43 deletions
2
Justfile
2
Justfile
|
@ -16,7 +16,7 @@ build:
|
||||||
CGO_ENABLED=1 go build -tags '{{TAGS}}' -ldflags '-s -w {{CGO_FLAGS}}' -v -o build/codeberg-pages-server ./
|
CGO_ENABLED=1 go build -tags '{{TAGS}}' -ldflags '-s -w {{CGO_FLAGS}}' -v -o build/codeberg-pages-server ./
|
||||||
|
|
||||||
build-tag VERSION:
|
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
|
lint: tool-golangci tool-gofumpt
|
||||||
[ $(gofumpt -extra -l . | wc -l) != 0 ] && { echo 'code not formated'; exit 1; }; \
|
[ $(gofumpt -extra -l . | wc -l) != 0 ] && { echo 'code not formated'; exit 1; }; \
|
||||||
|
|
|
@ -102,6 +102,7 @@ Thank you very much.
|
||||||
|
|
||||||
run `just dev`
|
run `just dev`
|
||||||
now this pages should work:
|
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/ci-testing/
|
||||||
- https://momar.localhost.mock.directory:4430/pag/@master/
|
- https://momar.localhost.mock.directory:4430/pag/@master/
|
||||||
|
- https://mock-pages.codeberg-test.org:4430/README.md
|
||||||
|
|
2
go.mod
2
go.mod
|
@ -1,6 +1,6 @@
|
||||||
module codeberg.org/codeberg/pages
|
module codeberg.org/codeberg/pages
|
||||||
|
|
||||||
go 1.19
|
go 1.20
|
||||||
|
|
||||||
require (
|
require (
|
||||||
code.gitea.io/sdk/gitea v0.15.1-0.20220729105105-cc14c63cccfa
|
code.gitea.io/sdk/gitea v0.15.1-0.20220729105105-cc14c63cccfa
|
||||||
|
|
|
@ -95,10 +95,9 @@ func TLSConfig(mainDomainSuffix string,
|
||||||
return tlsCertificate.(*tls.Certificate), nil
|
return tlsCertificate.(*tls.Certificate), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var tlsCertificate tls.Certificate
|
var tlsCertificate *tls.Certificate
|
||||||
var err error
|
var err error
|
||||||
var ok bool
|
if tlsCertificate, err = retrieveCertFromDB(sni, mainDomainSuffix, dnsProvider, acmeUseRateLimits, certDB); err != nil {
|
||||||
if tlsCertificate, ok = retrieveCertFromDB(sni, mainDomainSuffix, dnsProvider, acmeUseRateLimits, certDB); !ok {
|
|
||||||
// request a new certificate
|
// request a new certificate
|
||||||
if strings.EqualFold(sni, mainDomainSuffix) {
|
if strings.EqualFold(sni, mainDomainSuffix) {
|
||||||
return nil, errors.New("won't request certificate for main domain, something really bad has happened")
|
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 nil, err
|
||||||
}
|
}
|
||||||
return &tlsCertificate, nil
|
return tlsCertificate, nil
|
||||||
},
|
},
|
||||||
PreferServerCipherSuites: true,
|
|
||||||
NextProtos: []string{
|
NextProtos: []string{
|
||||||
"h2",
|
"h2",
|
||||||
"http/1.1",
|
"http/1.1",
|
||||||
|
@ -196,54 +194,53 @@ func (a AcmeHTTPChallengeProvider) CleanUp(domain, token, _ string) error {
|
||||||
return nil
|
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
|
// parse certificate from database
|
||||||
res, err := certDB.Get(sni)
|
res, err := certDB.Get(sni)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err) // TODO: no panic
|
return nil, err
|
||||||
}
|
} else if res == nil {
|
||||||
if res == nil {
|
return nil, database.ErrNotFound
|
||||||
return tls.Certificate{}, false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
tlsCertificate, err := tls.X509KeyPair(res.Certificate, res.PrivateKey)
|
tlsCertificate, err := tls.X509KeyPair(res.Certificate, res.PrivateKey)
|
||||||
if err != nil {
|
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
|
// TODO: document & put into own function
|
||||||
if !strings.EqualFold(sni, mainDomainSuffix) {
|
if !strings.EqualFold(sni, mainDomainSuffix) {
|
||||||
tlsCertificate.Leaf, err = x509.ParseCertificate(tlsCertificate.Certificate[0])
|
tlsCertificate.Leaf, err = x509.ParseCertificate(tlsCertificate.Certificate[0])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
return nil, fmt.Errorf("error parsin leaf tlsCert: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// renew certificates 7 days before they expire
|
// renew certificates 7 days before they expire
|
||||||
if tlsCertificate.Leaf.NotAfter.Before(time.Now().Add(7 * 24 * time.Hour)) {
|
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 {
|
if res.CSR != nil && len(res.CSR) > 0 {
|
||||||
// CSR stores the time when the renewal shall be tried again
|
// CSR stores the time when the renewal shall be tried again
|
||||||
nextTryUnix, err := strconv.ParseInt(string(res.CSR), 10, 64)
|
nextTryUnix, err := strconv.ParseInt(string(res.CSR), 10, 64)
|
||||||
if err == nil && time.Now().Before(time.Unix(nextTryUnix, 0)) {
|
if err == nil && time.Now().Before(time.Unix(nextTryUnix, 0)) {
|
||||||
return tlsCertificate, true
|
return &tlsCertificate, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// TODO: make a queue ?
|
||||||
go (func() {
|
go (func() {
|
||||||
res.CSR = nil // acme client doesn't like CSR to be set
|
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 := obtainCert(acmeClient, []string{sni}, res, "", dnsProvider, mainDomainSuffix, acmeUseRateLimits, certDB); err != nil {
|
||||||
if err != nil {
|
|
||||||
log.Error().Msgf("Couldn't renew certificate for %s: %v", sni, err)
|
log.Error().Msgf("Couldn't renew certificate for %s: %v", sni, err)
|
||||||
}
|
}
|
||||||
})()
|
})()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return tlsCertificate, true
|
return &tlsCertificate, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var obtainLocks = sync.Map{}
|
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], "*")
|
name := strings.TrimPrefix(domains[0], "*")
|
||||||
if dnsProvider == "" && len(domains[0]) > 0 && domains[0][0] == '*' {
|
if dnsProvider == "" && len(domains[0]) > 0 && domains[0][0] == '*' {
|
||||||
domains = domains[1:]
|
domains = domains[1:]
|
||||||
|
@ -256,16 +253,16 @@ func obtainCert(acmeClient *lego.Client, domains []string, renew *certificate.Re
|
||||||
time.Sleep(100 * time.Millisecond)
|
time.Sleep(100 * time.Millisecond)
|
||||||
_, working = obtainLocks.Load(name)
|
_, working = obtainLocks.Load(name)
|
||||||
}
|
}
|
||||||
cert, ok := retrieveCertFromDB(name, mainDomainSuffix, dnsProvider, acmeUseRateLimits, keyDatabase)
|
cert, err := retrieveCertFromDB(name, mainDomainSuffix, dnsProvider, acmeUseRateLimits, keyDatabase)
|
||||||
if !ok {
|
if err != nil {
|
||||||
return tls.Certificate{}, errors.New("certificate failed in synchronous request")
|
return nil, fmt.Errorf("certificate failed in synchronous request: %w", err)
|
||||||
}
|
}
|
||||||
return cert, nil
|
return cert, nil
|
||||||
}
|
}
|
||||||
defer obtainLocks.Delete(name)
|
defer obtainLocks.Delete(name)
|
||||||
|
|
||||||
if acmeClient == nil {
|
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
|
// request actual cert
|
||||||
|
@ -288,7 +285,7 @@ func obtainCert(acmeClient *lego.Client, domains []string, renew *certificate.Re
|
||||||
if res == nil {
|
if res == nil {
|
||||||
if user != "" {
|
if user != "" {
|
||||||
if err := checkUserLimit(user); err != nil {
|
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 != "" {
|
if renew != nil && renew.CertURL != "" {
|
||||||
tlsCertificate, err := tls.X509KeyPair(renew.Certificate, renew.PrivateKey)
|
tlsCertificate, err := tls.X509KeyPair(renew.Certificate, renew.PrivateKey)
|
||||||
if err != nil {
|
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)
|
leaf, err := leaf(&tlsCertificate)
|
||||||
if err == nil && leaf.NotAfter.After(time.Now()) {
|
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
|
// 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))
|
renew.CSR = []byte(strconv.FormatInt(time.Now().Add(6*time.Hour).Unix(), 10))
|
||||||
if err := keyDatabase.Put(name, renew); err != nil {
|
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 tlsCertificate, nil
|
return mockC, err
|
||||||
|
}
|
||||||
|
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)
|
log.Debug().Msgf("Obtained certificate for %v", domains)
|
||||||
|
|
||||||
if err := keyDatabase.Put(name, res); err != nil {
|
if err := keyDatabase.Put(name, res); err != nil {
|
||||||
return tls.Certificate{}, err
|
return nil, err
|
||||||
}
|
}
|
||||||
tlsCertificate, err := tls.X509KeyPair(res.Certificate, res.PrivateKey)
|
tlsCertificate, err := tls.X509KeyPair(res.Certificate, res.PrivateKey)
|
||||||
if err != nil {
|
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) {
|
func SetupAcmeConfig(acmeAPI, acmeMail, acmeEabHmac, acmeEabKID string, acmeAcceptTerms bool) (*lego.Config, error) {
|
||||||
|
|
|
@ -17,10 +17,10 @@ import (
|
||||||
"codeberg.org/codeberg/pages/server/database"
|
"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)
|
key, err := certcrypto.GeneratePrivateKey(certcrypto.RSA2048)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
template := x509.Certificate{
|
template := x509.Certificate{
|
||||||
|
@ -52,7 +52,7 @@ func mockCert(domain, msg, mainDomainSuffix string, keyDatabase database.CertDB)
|
||||||
key,
|
key,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
out := &bytes.Buffer{}
|
out := &bytes.Buffer{}
|
||||||
|
@ -61,7 +61,7 @@ func mockCert(domain, msg, mainDomainSuffix string, keyDatabase database.CertDB)
|
||||||
Type: "CERTIFICATE",
|
Type: "CERTIFICATE",
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
return nil, err
|
||||||
}
|
}
|
||||||
outBytes := out.Bytes()
|
outBytes := out.Bytes()
|
||||||
res := &certificate.Resource{
|
res := &certificate.Resource{
|
||||||
|
@ -80,7 +80,7 @@ func mockCert(domain, msg, mainDomainSuffix string, keyDatabase database.CertDB)
|
||||||
|
|
||||||
tlsCertificate, err := tls.X509KeyPair(res.Certificate, res.PrivateKey)
|
tlsCertificate, err := tls.X509KeyPair(res.Certificate, res.PrivateKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
return nil, err
|
||||||
}
|
}
|
||||||
return tlsCertificate
|
return &tlsCertificate, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,7 +29,6 @@ type Cert struct {
|
||||||
PrivateKey []byte `xorm:"'private_key'"`
|
PrivateKey []byte `xorm:"'private_key'"`
|
||||||
Certificate []byte `xorm:"'certificate'"`
|
Certificate []byte `xorm:"'certificate'"`
|
||||||
IssuerCertificate []byte `xorm:"'issuer_certificate'"`
|
IssuerCertificate []byte `xorm:"'issuer_certificate'"`
|
||||||
CSR []byte `xorm:"'csr'"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c Cert) Raw() *certificate.Resource {
|
func (c Cert) Raw() *certificate.Resource {
|
||||||
|
@ -40,7 +39,6 @@ func (c Cert) Raw() *certificate.Resource {
|
||||||
PrivateKey: c.PrivateKey,
|
PrivateKey: c.PrivateKey,
|
||||||
Certificate: c.Certificate,
|
Certificate: c.Certificate,
|
||||||
IssuerCertificate: c.IssuerCertificate,
|
IssuerCertificate: c.IssuerCertificate,
|
||||||
CSR: c.CSR,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -71,6 +69,5 @@ func toCert(name string, c *certificate.Resource) (*Cert, error) {
|
||||||
PrivateKey: c.PrivateKey,
|
PrivateKey: c.PrivateKey,
|
||||||
Certificate: c.Certificate,
|
Certificate: c.Certificate,
|
||||||
IssuerCertificate: c.IssuerCertificate,
|
IssuerCertificate: c.IssuerCertificate,
|
||||||
CSR: c.CSR,
|
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue